44 Commits
0.4.8 ... 0.4.9

Author SHA1 Message Date
Johns
74a62e3649 Makes audio ts parser default. Suspend fixes. 2012-03-03 18:47:07 +01:00
Johns
7e1a42f7ed Experimental ac3 audio drift correction support. 2012-03-03 16:45:59 +01:00
Johns
dda9011abc Removes LPCM detection from TS parser. 2012-03-03 16:11:38 +01:00
Johns
de79e9211f Disabled audio drift correction as default. 2012-03-02 18:17:51 +01:00
Johns
b0d9f41020 Rewrote video/audio start code. 2012-03-02 18:16:20 +01:00
Johns
4d1a516c80 Fix Bug: PES audio buffer not correct reset. 2012-03-02 16:06:45 +01:00
Johns
995f1286bd Fix attach. 2012-03-02 00:38:52 +01:00
Johns
fd0ae12f24 Fix warning. 2012-03-02 00:28:53 +01:00
Johns
db258a0fbd Detach/Attach on MakePrimaryDevice. 2012-03-02 00:22:08 +01:00
Johns
0df8e8a5fc Handle initial suspend mode like normal suspend. 2012-03-02 00:05:03 +01:00
Johns
6a28064dce Add support for attach/detach plugin. 2012-03-01 22:12:22 +01:00
Johns
b5e9077c74 Increase AudioBufferTime for OSS. 2012-03-01 17:50:57 +01:00
Johns
3b4ace14cf Add ac3 to info message for pass-through. 2012-02-29 18:21:28 +01:00
Johns
5aa868c296 Don't change correction value during pass-through. 2012-02-29 17:40:58 +01:00
Johns
43b48224b5 Improved audio drift correction support. 2012-02-29 16:35:49 +01:00
Johns
144f22314f Experimental audio drift correction support. 2012-02-27 23:13:53 +01:00
Johns
51eb720265 VideoSetFullscreen needs X11 connection. 2012-02-26 20:54:31 +01:00
Johns
e977007dd3 Removed old cruft. 2012-02-26 14:30:46 +01:00
Johns
769f00b4f6 Try to restart alsa after underrun. 2012-02-25 18:10:19 +01:00
Johns
aa426cd8b2 Add SVDRP HOTK command support and cleanup. 2012-02-25 13:02:15 +01:00
Johns
b2cab00599 Remove AVDictionary. 2012-02-24 18:16:24 +01:00
Johns
b54d62ef35 Video background color documentation. 2012-02-24 15:42:32 +01:00
Johns
9b68248a3e Increased audio buffer time for PES packets. 2012-02-24 15:41:17 +01:00
Johns
762959fbb4 Only a single frame is supported. 2012-02-24 15:38:04 +01:00
Johns
07b426f2b5 Fix bug in new audio ts parser: hangup. 2012-02-24 15:22:26 +01:00
Johns
668a6ec277 Support configuration and set of video background. 2012-02-24 15:15:50 +01:00
Johns
82f61de117 Include GIT version, when build from git. 2012-02-23 23:33:00 +01:00
Johns
67e571f02b Survive lost X11 display. 2012-02-23 17:57:21 +01:00
Johns
c17af0e958 Fix bug: 100% cpu use with plugins like mp3. 2012-02-23 15:32:43 +01:00
Johns
2561214c3e Info time should be 1 minute and not ~1 second. 2012-02-22 18:37:50 +01:00
Johns
7382bd60ff VA-API branch staging support. 2012-02-22 16:50:35 +01:00
Johns
73b93f1aba Makes A/V sync info time configurable. 2012-02-22 16:32:40 +01:00
Johns
0243b1c8a7 Fix bug: No OSD until valid video stream shown. 2012-02-22 15:10:47 +01:00
Johns
6ce760ccd8 60Hz display mode configurable with setup.conf. 2012-02-22 15:06:05 +01:00
Johns
2f869884ba Support downmix of AC-3 to stero. 2012-02-21 22:36:10 +01:00
Johns
5d8dea1b6b New audio PES handling.
New easier and more flexible audio PES packet parser, which includes own
codec parser.
Removed av_parser use.
Reduced audio buffer time, faster channel switch.
New audio transport stream parser (not enabled as default).
2012-02-21 20:55:28 +01:00
Johns
1f232db5b4 Nicer debug output when clock out of range. 2012-02-19 22:45:29 +01:00
Johns
c4ad13c53f Fix bug: Grabbing JPG image fails while suspended. 2012-02-19 20:52:57 +01:00
Johns
98f73f2199 Add support for hot keys. 2012-02-19 19:22:03 +01:00
Johns
89ca44206c Add support to use characters input in edit mode. 2012-02-17 16:37:38 +01:00
Johns
5c9b85b69b Use SetVideoFormat to call SetVideoDisplayFormat. 2012-02-17 15:10:24 +01:00
Johns
09cfab3856 Add posibility to disable repeat pict warning. 2012-02-16 21:55:14 +01:00
Johns
30e903d90a Wakeup audio thread after pause. 2012-02-16 18:41:46 +01:00
Johns
852d367225 Adds trick speed support. 2012-02-16 15:31:53 +01:00
13 changed files with 2475 additions and 876 deletions

View File

@@ -1,3 +1,28 @@
User johns
Date:
Experimental ac3 audio drift correction support.
Removes LPCM detection from TS parser.
Rewrote video/audio start code.
Add support for attach/detach plugin.
OSS needs bigger audio buffers.
Improved audio drift correction support.
Experimental audio drift correction support.
Add SVDRP HOTK command support.
Increased audio buffer time for PES packets.
Support configuration and set of video background.
Survive lost X11 display.
Fix bug: 100% cpu use with plugins like mp3.
Wakeup display thread on channel switch, osd can now be shown without
video.
Makes 60Hz display mode configurable with setup.conf.
Support downmix of AC-3 to stero.
New audio PES packet parser.
Fix bug: Grabbing a JPG image fails while suspended.
Add support for hot keys.
Add support to use characters input in edit mode.
Adds trick speed support.
User johns User johns
Date: Thu Feb 16 09:59:14 CET 2012 Date: Thu Feb 16 09:59:14 CET 2012

View File

@@ -19,8 +19,12 @@ GIT_REV = $(shell git describe --always 2>/dev/null)
### Configuration (edit this for your needs) ### Configuration (edit this for your needs)
CONFIG := #-DDEBUG CONFIG := #-DDEBUG
CONFIG += -DAV_INFO #CONFIG += -DUSE_AUDIO_DRIFT_CORRECTION # build new audio drift code
#CONFIG += -DHAVE_PTHREAD_NAME #CONFIG += -DUSE_AC3_DRIFT_CORRECTION # build new ac-3 drift code
CONFIG += -DAV_INFO -DAV_INFO_TIME=3000 # debug a/v sync
#CONFIG += -DHAVE_PTHREAD_NAME # supports new pthread_setname_np
#CONFIG += -DNO_TS_AUDIO # disable ts audio parser
#CONFIG += -DUSE_TS_VIDEO # build new ts video parser
CONFIG += $(shell pkg-config --exists vdpau && echo "-DUSE_VDPAU") CONFIG += $(shell pkg-config --exists vdpau && echo "-DUSE_VDPAU")
CONFIG += $(shell pkg-config --exists libva && echo "-DUSE_VAAPI") CONFIG += $(shell pkg-config --exists libva && echo "-DUSE_VAAPI")
CONFIG += $(shell pkg-config --exists alsa && echo "-DUSE_ALSA") CONFIG += $(shell pkg-config --exists alsa && echo "-DUSE_ALSA")
@@ -66,7 +70,7 @@ DEFINES += $(CONFIG) -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' \
$(if $(GIT_REV), -DGIT_REV='"$(GIT_REV)"') $(if $(GIT_REV), -DGIT_REV='"$(GIT_REV)"')
_CFLAGS = $(DEFINES) $(INCLUDES) \ _CFLAGS = $(DEFINES) $(INCLUDES) \
$(shell pkg-config --cflags libavcodec libavformat) \ $(shell pkg-config --cflags libavcodec) \
`pkg-config --cflags x11 x11-xcb xcb xcb-xv xcb-shm xcb-dpms xcb-atom\ `pkg-config --cflags x11 x11-xcb xcb xcb-xv xcb-shm xcb-dpms xcb-atom\
xcb-screensaver xcb-randr xcb-glx xcb-icccm xcb-keysyms`\ xcb-screensaver xcb-randr xcb-glx xcb-icccm xcb-keysyms`\
`pkg-config --cflags gl glu` \ `pkg-config --cflags gl glu` \
@@ -82,7 +86,7 @@ override CXXFLAGS += $(_CFLAGS)
override CFLAGS += $(_CFLAGS) override CFLAGS += $(_CFLAGS)
LIBS += -lrt \ LIBS += -lrt \
$(shell pkg-config --libs libavcodec libavformat) \ $(shell pkg-config --libs libavcodec) \
`pkg-config --libs x11 x11-xcb xcb xcb-xv xcb-shm xcb-dpms xcb-atom\ `pkg-config --libs x11 x11-xcb xcb xcb-xv xcb-shm xcb-dpms xcb-atom\
xcb-screensaver xcb-randr xcb-glx xcb-icccm xcb-keysyms`\ xcb-screensaver xcb-randr xcb-glx xcb-icccm xcb-keysyms`\
`pkg-config --libs gl glu` \ `pkg-config --libs gl glu` \

View File

@@ -139,12 +139,17 @@ Setup: /etc/vdr/setup.conf
softhddevice.AudioDelay = 0 softhddevice.AudioDelay = 0
+n or -n ms +n or -n ms
delay audio or delay video
softhddevice.AudioPassthrough = 0 softhddevice.AudioPassthrough = 0
0 = none, 1 = AC-3 0 = none, 1 = AC-3
for AC-3 the pass-through device is used. for AC-3 the pass-through device is used.
softhddevice.AudioDownmix = 0
0 = none, 1 = downmix
downmix AC-3 to stero.
softhddevice.AutoCrop.Interval = 0 softhddevice.AutoCrop.Interval = 0
0 disables auto-crop 0 disables auto-crop
n each 'n' frames auto-crop is checked. n each 'n' frames auto-crop is checked.
@@ -157,6 +162,12 @@ Setup: /etc/vdr/setup.conf
if detected crop area is too small, cut max 'n' pixels at top and if detected crop area is too small, cut max 'n' pixels at top and
bottom. bottom.
softhddevice.Background = 0
32bit RGBA background color
(Red * 16777216 + Green * 65536 + Blue * 256 + Alpha)
or hex RRGGBBAA
grey = 2155905279
softhddevice.SkipLines = 0 softhddevice.SkipLines = 0
skip 'n' lines at top and bottom of the video picture. skip 'n' lines at top and bottom of the video picture.
@@ -171,6 +182,10 @@ Setup: /etc/vdr/setup.conf
softhddevice.Suspend.X11 = 0 softhddevice.Suspend.X11 = 0
1 suspend stops X11 server (not working yet) 1 suspend stops X11 server (not working yet)
softhddevice.60HzMode = 0
0 disable 60Hz display mode
1 enable 60Hz display mode
VideoDisplayFormat = ? VideoDisplayFormat = ?
0 pan and scan 0 pan and scan
1 letter box 1 letter box
@@ -209,8 +224,20 @@ Commandline:
SVDRP: SVDRP:
------ ------
Use 'svdrpsend.pl plug softhddevice HELP' to see the SVDRP commands Use 'svdrpsend.pl plug softhddevice HELP'
help and which are supported by the plugin. or 'svdrpsend plug softhddevice HELP' to see the SVDRP commands help
and which are supported by the plugin.
Keymacros:
----------
See keymacros.conf how to setup the macros.
This are the supported key sequences:
@softhddevice Blue 1 0 disable pass-through
@softhddevice Blue 1 1 enable pass-through
@softhddevice Blue 1 2 toggle pass-through
Running: Running:
-------- --------

18
Todo
View File

@@ -25,7 +25,8 @@ missing:
suspend plugin didn't restore full-screen (is this wanted?) suspend plugin didn't restore full-screen (is this wanted?)
Option deinterlace off / deinterlace force! Option deinterlace off / deinterlace force!
ColorSpace aren't configurable with the gui. ColorSpace aren't configurable with the gui.
Inverse telecine isn't configurable with the gui. Replay of old vdr 1.6 recordings.
svdrp support for hot-keys.
crash: crash:
AudioPlayHandlerThread -> pthread_cond_wait AudioPlayHandlerThread -> pthread_cond_wait
@@ -36,9 +37,10 @@ video:
reduce warnings after channel switch reduce warnings after channel switch
grab image with hardware and better scaling support grab image with hardware and better scaling support
hard channel switch hard channel switch
OSD can only be shown after some stream could be shown
yaepghd changed position is lost on channel switch yaepghd changed position is lost on channel switch
pause (live tv) has sometime problems with SAT1 HD Pro7 HD pause (live tv) has sometime problems with SAT1 HD Pro7 HD
radio show black background
radio no need to wait on video buffers
vdpau: vdpau:
software decoder path not working software decoder path not working
@@ -76,13 +78,16 @@ x11:
support embedded mode support embedded mode
audio: audio:
write TS -> PES parser, which feeds audio before the next start packet
Combine alsa+oss ringbuffer code. Combine alsa+oss ringbuffer code.
Make alsa thread/polled and oss thread/polled output module runtime Make alsa thread/polled and oss thread/polled output module runtime
selectable. selectable.
software volume support (could be done with asound.conf) software volume support (could be done with asound.conf)
Mute should do a real mute and not only set volume to zero. Mute should do a real mute and not only set volume to zero.
Starting suspended and muted, didn't register the mute. Starting suspended and muted, didn't register the mute.
Relaxed audio sync checks at end of packet and already in sync
samplerate problem resume/suspend.
only wait for video start, if video is running.
Not primary device, don't use and block audio/video.
audio/alsa: audio/alsa:
better downmix of >2 channels on 2 channel hardware better downmix of >2 channels on 2 channel hardware
@@ -99,8 +104,11 @@ HDMI/SPDIF Passthrough:
only AC-3 written only AC-3 written
playback of recording playback of recording
pause is not reset, when replay exit pause is not reset, when replay exit (fixed?)
replay/pause need 100% cpu replay/pause need 100% cpu (fixed?)
plugins:
mp3 plugin needs 100% cpu (OSD updates?)
setup: setup:
Setup of decoder type. Setup of decoder type.

361
audio.c
View File

@@ -107,9 +107,11 @@ typedef struct _audio_module_
void (*Thread) (void); ///< module thread handler void (*Thread) (void); ///< module thread handler
void (*Enqueue) (const void *, int); ///< enqueue samples for output void (*Enqueue) (const void *, int); ///< enqueue samples for output
void (*VideoReady) (void); ///< video ready, start audio
void (*FlushBuffers) (void); ///< flush sample buffers void (*FlushBuffers) (void); ///< flush sample buffers
void (*Poller) (void); ///< output poller void (*Poller) (void); ///< output poller
int (*FreeBytes) (void); ///< number of bytes free in buffer 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 uint64_t(*GetDelay) (void); ///< get current audio delay
void (*SetVolume) (int); ///< set output volume void (*SetVolume) (int); ///< set output volume
int (*Setup) (int *, int *, int); ///< setup channels, samplerate int (*Setup) (int *, int *, int); ///< setup channels, samplerate
@@ -137,11 +139,12 @@ static const char *AudioMixerDevice; ///< alsa/OSS mixer device name
static const char *AudioMixerChannel; ///< alsa/OSS mixer channel name static const char *AudioMixerChannel; ///< alsa/OSS mixer channel name
static volatile char AudioRunning; ///< thread running / stopped static volatile char AudioRunning; ///< thread running / stopped
static volatile char AudioPaused; ///< audio paused static volatile char AudioPaused; ///< audio paused
static volatile char AudioVideoIsReady; ///< video ready start early
static unsigned AudioSampleRate; ///< audio sample rate in hz static unsigned AudioSampleRate; ///< audio sample rate in hz
static unsigned AudioChannels; ///< number of audio channels static unsigned AudioChannels; ///< number of audio channels
static const int AudioBytesProSample = 2; ///< number of bytes per sample static const int AudioBytesProSample = 2; ///< number of bytes per sample
static int64_t AudioPTS; ///< audio pts clock static int64_t AudioPTS; ///< audio pts clock
static const int AudioBufferTime = 350; ///< audio buffer time in ms static int AudioBufferTime = 336; ///< audio buffer time in ms
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
static pthread_t AudioThread; ///< audio play thread static pthread_t AudioThread; ///< audio play thread
@@ -151,7 +154,7 @@ static pthread_cond_t AudioStartCond; ///< condition variable
static const int AudioThread; ///< dummy audio thread static const int AudioThread; ///< dummy audio thread
#endif #endif
extern int VideoAudioDelay; /// import audio/video delay extern int VideoAudioDelay; ///< import audio/video delay
#ifdef USE_AUDIORING #ifdef USE_AUDIORING
@@ -289,15 +292,19 @@ static int AlsaAddToRingbuffer(const void *samples, int count)
// too many bytes are lost // too many bytes are lost
// FIXME: should skip more, longer skip, but less often? // FIXME: should skip more, longer skip, but less often?
} }
// 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 *
AudioBytesProSample);
}
if (!AudioRunning) { if (!AudioRunning) {
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) { Debug(4, "audio/alsa: start %4zdms\n",
(RingBufferUsedBytes(AlsaRingBuffer) * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
// forced start
if (AlsaStartThreshold * 2 < RingBufferUsedBytes(AlsaRingBuffer)) {
return 1;
}
// enough video + audio buffered
if (AudioVideoIsReady
&& AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
// restart play-back // restart play-back
return 1; return 1;
} }
@@ -326,7 +333,8 @@ static int AlsaPlayRingbuffer(void)
if (n == -EAGAIN) { if (n == -EAGAIN) {
continue; continue;
} }
Error(_("audio/alsa: underrun error?\n")); Error(_("audio/alsa: avail underrun error? '%s'\n"),
snd_strerror(n));
err = snd_pcm_recover(AlsaPCMHandle, n, 0); err = snd_pcm_recover(AlsaPCMHandle, n, 0);
if (err >= 0) { if (err >= 0) {
continue; continue;
@@ -342,6 +350,15 @@ static int AlsaPlayRingbuffer(void)
if (AudioThread) { if (AudioThread) {
if (!AudioAlsaDriverBroken) { if (!AudioAlsaDriverBroken) {
Error(_("audio/alsa: broken driver %d\n"), avail); Error(_("audio/alsa: broken driver %d\n"), avail);
Error("audio/alsa: state %s\n",
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
}
if (snd_pcm_state(AlsaPCMHandle)
== SND_PCM_STATE_PREPARED) {
if ((err = snd_pcm_start(AlsaPCMHandle)) < 0) {
Error(_("audio/alsa: snd_pcm_start(): %s\n"),
snd_strerror(err));
}
} }
usleep(5 * 1000); usleep(5 * 1000);
} }
@@ -353,6 +370,9 @@ static int AlsaPlayRingbuffer(void)
n = RingBufferGetReadPointer(AlsaRingBuffer, &p); n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
if (!n) { // ring buffer empty if (!n) { // ring buffer empty
if (first) { // only error on first loop if (first) { // only error on first loop
Debug(4, "audio/alsa: empty buffers %d\n", avail);
// ring buffer empty
// AlsaLowWaterMark = 1;
return 1; return 1;
} }
return 0; return 0;
@@ -382,7 +402,8 @@ static int AlsaPlayRingbuffer(void)
goto again; goto again;
} }
*/ */
Error(_("audio/alsa: underrun error?\n")); Error(_("audio/alsa: writei underrun error? '%s'\n"),
snd_strerror(err));
err = snd_pcm_recover(AlsaPCMHandle, err, 0); err = snd_pcm_recover(AlsaPCMHandle, err, 0);
if (err >= 0) { if (err >= 0) {
goto again; goto again;
@@ -414,7 +435,7 @@ static void AlsaFlushBuffers(void)
RingBufferReadAdvance(AlsaRingBuffer, RingBufferReadAdvance(AlsaRingBuffer,
RingBufferUsedBytes(AlsaRingBuffer)); RingBufferUsedBytes(AlsaRingBuffer));
state = snd_pcm_state(AlsaPCMHandle); state = snd_pcm_state(AlsaPCMHandle);
Debug(3, "audio/alsa: state %d - %s\n", state, Debug(3, "audio/alsa: flush state %d - %s\n", state,
snd_pcm_state_name(state)); snd_pcm_state_name(state));
if (state != SND_PCM_STATE_OPEN) { if (state != SND_PCM_STATE_OPEN) {
if ((err = snd_pcm_drop(AlsaPCMHandle)) < 0) { if ((err = snd_pcm_drop(AlsaPCMHandle)) < 0) {
@@ -427,6 +448,7 @@ static void AlsaFlushBuffers(void)
} }
} }
AudioRunning = 0; AudioRunning = 0;
AudioVideoIsReady = 0;
AudioPTS = INT64_C(0x8000000000000000); AudioPTS = INT64_C(0x8000000000000000);
} }
@@ -451,6 +473,14 @@ static int AlsaFreeBytes(void)
return AlsaRingBuffer ? RingBufferFreeBytes(AlsaRingBuffer) : INT32_MAX; return AlsaRingBuffer ? RingBufferFreeBytes(AlsaRingBuffer) : INT32_MAX;
} }
/**
** Get used bytes in audio output.
*/
static int AlsaUsedBytes(void)
{
return AlsaRingBuffer ? RingBufferUsedBytes(AlsaRingBuffer) : 0;
}
#if 0 #if 0
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -573,8 +603,6 @@ static void AlsaEnqueue(const void *samples, int count)
Debug(3, "audio/alsa: unpaused\n"); Debug(3, "audio/alsa: unpaused\n");
} }
} }
// Update audio clock
// AudioPTS += (size * 90000) / (AudioSampleRate * AudioChannels * AudioBytesProSample);
} }
#endif #endif
@@ -633,14 +661,15 @@ static void AlsaThread(void)
break; break;
} }
// wait for space in kernel buffers // wait for space in kernel buffers
if ((err = snd_pcm_wait(AlsaPCMHandle, 100)) < 0) { if ((err = snd_pcm_wait(AlsaPCMHandle, 24)) < 0) {
Error(_("audio/alsa: wait underrun error?\n")); Error(_("audio/alsa: wait underrun error? '%s'\n"),
snd_strerror(err));
err = snd_pcm_recover(AlsaPCMHandle, err, 0); err = snd_pcm_recover(AlsaPCMHandle, err, 0);
if (err >= 0) { if (err >= 0) {
continue; continue;
} }
Error(_("audio/alsa: snd_pcm_wait(): %s\n"), snd_strerror(err)); Error(_("audio/alsa: snd_pcm_wait(): %s\n"), snd_strerror(err));
usleep(100 * 1000); usleep(24 * 1000);
continue; continue;
} }
if (AlsaFlushBuffer || AudioPaused) { if (AlsaFlushBuffer || AudioPaused) {
@@ -654,11 +683,12 @@ static void AlsaThread(void)
} }
state = snd_pcm_state(AlsaPCMHandle); state = snd_pcm_state(AlsaPCMHandle);
if (state != SND_PCM_STATE_RUNNING) { if (state != SND_PCM_STATE_RUNNING) {
Debug(3, "audio/alsa: stopping play\n"); Debug(3, "audio/alsa: stopping play '%s'\n",
snd_pcm_state_name(state));
break; break;
} }
pthread_yield(); pthread_yield();
usleep(20 * 1000); // let fill/empty the buffers usleep(24 * 1000); // let fill/empty the buffers
} }
} }
} }
@@ -687,6 +717,26 @@ static void AlsaThreadEnqueue(const void *samples, int count)
} }
} }
/**
** Video is ready, start audio if possible,
*/
static void AlsaVideoReady(void)
{
if (AudioSampleRate && AudioChannels) {
Debug(3, "audio/alsa: start %4zdms video start\n",
(RingBufferUsedBytes(AlsaRingBuffer) * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
}
if (!AudioRunning) {
// enough video + audio buffered
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
AudioRunning = 1;
pthread_cond_signal(&AudioStartCond);
}
}
}
/** /**
** Flush alsa buffers with thread. ** Flush alsa buffers with thread.
*/ */
@@ -720,12 +770,12 @@ static snd_pcm_t *AlsaOpenPCM(int use_ac3)
// &&|| hell // &&|| hell
if (!(use_ac3 && ((device = AudioAC3Device) if (!(use_ac3 && ((device = AudioAC3Device)
|| (device = getenv("ALSA_AC3_DEVICE")) || (device = getenv("ALSA_AC3_DEVICE"))))
|| (device = getenv("ALSA_PASSTHROUGH_DEVICE"))))
&& !(device = AudioPCMDevice) && !(device = getenv("ALSA_DEVICE"))) { && !(device = AudioPCMDevice) && !(device = getenv("ALSA_DEVICE"))) {
device = "default"; device = "default";
} }
Debug(3, "audio/alsa: &&|| hell '%s'\n", device); Info(_("audio/alsa: using %sdevice '%s'\n"), use_ac3 ? "ac3 " : "",
device);
// open none blocking; if device is already used, we don't want wait // open none blocking; if device is already used, we don't want wait
if ((err = if ((err =
@@ -752,7 +802,8 @@ static void AlsaInitPCM(void)
snd_pcm_t *handle; snd_pcm_t *handle;
snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_t *hw_params;
int err; int err;
snd_pcm_uframes_t buffer_size;
//snd_pcm_uframes_t buffer_size;
if (!(handle = AlsaOpenPCM(0))) { if (!(handle = AlsaOpenPCM(0))) {
return; return;
@@ -767,8 +818,9 @@ static void AlsaInitPCM(void)
} }
AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params); AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params);
Info(_("audio/alsa: supports pause: %s\n"), AlsaCanPause ? "yes" : "no"); Info(_("audio/alsa: supports pause: %s\n"), AlsaCanPause ? "yes" : "no");
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size); // needs audio setup
Info(_("audio/alsa: max buffer size %lu\n"), buffer_size); //snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
//Info(_("audio/alsa: max buffer size %lu\n"), buffer_size);
AlsaPCMHandle = handle; AlsaPCMHandle = handle;
} }
@@ -868,6 +920,9 @@ static uint64_t AlsaGetDelay(void)
if (!AlsaPCMHandle || !AudioSampleRate) { if (!AlsaPCMHandle || !AudioSampleRate) {
return 0UL; return 0UL;
} }
if (!AudioRunning) { // audio not running
return 0UL;
}
// FIXME: thread safe? __assert_fail_base in snd_pcm_delay // FIXME: thread safe? __assert_fail_base in snd_pcm_delay
// delay in frames in alsa + kernel buffers // delay in frames in alsa + kernel buffers
@@ -941,7 +996,7 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16, snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1, SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1,
125 * 1000))) { 96 * 1000))) {
Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err)); Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
/* /*
@@ -1091,16 +1146,21 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
// update buffer // update buffer
snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size); snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size);
Info(_("audio/alsa: buffer size %lu, period size %lu\n"), buffer_size, Info(_("audio/alsa: buffer size %lu %zdms, period size %lu %zdms\n"),
period_size); buffer_size, snd_pcm_frames_to_bytes(AlsaPCMHandle,
buffer_size) * 1000 / (AudioSampleRate * AudioChannels *
AudioBytesProSample), period_size,
snd_pcm_frames_to_bytes(AlsaPCMHandle,
period_size) * 1000 / (AudioSampleRate * AudioChannels *
AudioBytesProSample));
Debug(3, "audio/alsa: state %s\n", Debug(3, "audio/alsa: state %s\n",
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle))); snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
AlsaStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, period_size); AlsaStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, period_size);
// buffer time/delay in ms // buffer time/delay in ms
delay = AudioBufferTime; delay = AudioBufferTime;
if (VideoAudioDelay > -100) { if (VideoAudioDelay > 0) {
delay += 100 + VideoAudioDelay / 90; delay += VideoAudioDelay / 90;
} }
if (AlsaStartThreshold < if (AlsaStartThreshold <
(*freq * *channels * AudioBytesProSample * delay) / 1000U) { (*freq * *channels * AudioBytesProSample * delay) / 1000U) {
@@ -1216,13 +1276,16 @@ static const AudioModule AlsaModule = {
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
.Thread = AlsaThread, .Thread = AlsaThread,
.Enqueue = AlsaThreadEnqueue, .Enqueue = AlsaThreadEnqueue,
.VideoReady = AlsaVideoReady,
.FlushBuffers = AlsaThreadFlushBuffers, .FlushBuffers = AlsaThreadFlushBuffers,
#else #else
.Enqueue = AlsaEnqueue, .Enqueue = AlsaEnqueue,
.VideoReady = AlsaVideoReady,
.FlushBuffers = AlsaFlushBuffers, .FlushBuffers = AlsaFlushBuffers,
#endif #endif
.Poller = AlsaPoller, .Poller = AlsaPoller,
.FreeBytes = AlsaFreeBytes, .FreeBytes = AlsaFreeBytes,
.UsedBytes = AlsaUsedBytes,
.GetDelay = AlsaGetDelay, .GetDelay = AlsaGetDelay,
.SetVolume = AlsaSetVolume, .SetVolume = AlsaSetVolume,
.Setup = AlsaSetup, .Setup = AlsaSetup,
@@ -1248,6 +1311,7 @@ static int OssPcmFildes = -1; ///< pcm file descriptor
static int OssMixerFildes = -1; ///< mixer file descriptor static int OssMixerFildes = -1; ///< mixer file descriptor
static int OssMixerChannel; ///< mixer channel index static int OssMixerChannel; ///< mixer channel index
static RingBuffer *OssRingBuffer; ///< audio ring buffer static RingBuffer *OssRingBuffer; ///< audio ring buffer
static int OssFragmentTime; ///< fragment time in ms
static unsigned OssStartThreshold; ///< start play, if filled static unsigned OssStartThreshold; ///< start play, if filled
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
@@ -1276,15 +1340,19 @@ static int OssAddToRingbuffer(const void *samples, int count)
// too many bytes are lost // too many bytes are lost
// FIXME: should skip more, longer skip, but less often? // FIXME: should skip more, longer skip, but less often?
} }
// 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 *
AudioBytesProSample);
}
if (!AudioRunning) { if (!AudioRunning) {
if (OssStartThreshold < RingBufferUsedBytes(OssRingBuffer)) { Debug(4, "audio/oss: start %4zdms\n",
(RingBufferUsedBytes(OssRingBuffer) * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
// forced start
if (OssStartThreshold * 2 < RingBufferUsedBytes(OssRingBuffer)) {
return 1;
}
// enough video + audio buffered
if (AudioVideoIsReady
&& OssStartThreshold < RingBufferUsedBytes(OssRingBuffer)) {
// restart play-back // restart play-back
return 1; return 1;
} }
@@ -1333,7 +1401,7 @@ static int OssPlayRingbuffer(void)
Error(_("audio/oss: write error: %s\n"), strerror(errno)); Error(_("audio/oss: write error: %s\n"), strerror(errno));
return 1; return 1;
} }
Error(_("audio/oss: error not all bytes written\n")); Warning(_("audio/oss: error not all bytes written\n"));
} }
// advance how many could written // advance how many could written
RingBufferReadAdvance(OssRingBuffer, n); RingBufferReadAdvance(OssRingBuffer, n);
@@ -1358,6 +1426,7 @@ static void OssFlushBuffers(void)
} }
} }
AudioRunning = 0; AudioRunning = 0;
AudioVideoIsReady = 0;
AudioPTS = INT64_C(0x8000000000000000); AudioPTS = INT64_C(0x8000000000000000);
} }
@@ -1416,6 +1485,14 @@ static int OssFreeBytes(void)
return OssRingBuffer ? RingBufferFreeBytes(OssRingBuffer) : INT32_MAX; return OssRingBuffer ? RingBufferFreeBytes(OssRingBuffer) : INT32_MAX;
} }
/**
** Get used bytes in audio output.
*/
static int OssUsedBytes(void)
{
return OssRingBuffer ? RingBufferUsedBytes(OssRingBuffer) : 0;
}
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -1446,10 +1523,10 @@ static void OssThread(void)
fds[0].fd = OssPcmFildes; fds[0].fd = OssPcmFildes;
fds[0].events = POLLOUT | POLLERR; fds[0].events = POLLOUT | POLLERR;
// wait for space in kernel buffers // wait for space in kernel buffers
err = poll(fds, 1, 100); err = poll(fds, 1, OssFragmentTime);
if (err < 0) { if (err < 0) {
Error(_("audio/oss: error poll %s\n"), strerror(errno)); Error(_("audio/oss: error poll %s\n"), strerror(errno));
usleep(100 * 1000); usleep(OssFragmentTime * 1000);
continue; continue;
} }
@@ -1462,7 +1539,7 @@ static void OssThread(void)
break; break;
} }
pthread_yield(); pthread_yield();
usleep(20 * 1000); // let fill/empty the buffers usleep(OssFragmentTime * 1000); // let fill/empty the buffers
} }
} }
} }
@@ -1486,6 +1563,26 @@ static void OssThreadEnqueue(const void *samples, int count)
} }
} }
/**
** Video is ready, start audio if possible,
*/
static void OssVideoReady(void)
{
if (AudioSampleRate && AudioChannels) {
Debug(3, "audio/oss: start %4zdms video start\n",
(RingBufferUsedBytes(OssRingBuffer) * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
}
if (!AudioRunning) {
// enough video + audio buffered
if (OssStartThreshold < RingBufferUsedBytes(OssRingBuffer)) {
AudioRunning = 1;
pthread_cond_signal(&AudioStartCond);
}
}
}
/** /**
** Flush OSS buffers with thread. ** Flush OSS buffers with thread.
*/ */
@@ -1522,7 +1619,7 @@ static int OssOpenPCM(int use_ac3)
&& !(device = AudioPCMDevice) && !(device = getenv("OSS_AUDIODEV"))) { && !(device = AudioPCMDevice) && !(device = getenv("OSS_AUDIODEV"))) {
device = "/dev/dsp"; device = "/dev/dsp";
} }
Debug(3, "audio/oss: &&|| hell '%s'\n", device); Info(_("audio/oss: using %sdevice '%s'\n"), use_ac3 ? "ac3" : "", device);
if ((fildes = open(device, O_WRONLY)) < 0) { if ((fildes = open(device, O_WRONLY)) < 0) {
Error(_("audio/oss: can't open dsp device '%s': %s\n"), device, Error(_("audio/oss: can't open dsp device '%s': %s\n"), device,
@@ -1642,8 +1739,7 @@ static uint64_t OssGetDelay(void)
if (OssPcmFildes == -1) { // setup failure if (OssPcmFildes == -1) { // setup failure
return 0UL; return 0UL;
} }
if (!AudioRunning) { // audio not running
if (!AudioRunning) {
return 0UL; return 0UL;
} }
// delay in bytes in kernel buffers // delay in bytes in kernel buffers
@@ -1653,18 +1749,14 @@ static uint64_t OssGetDelay(void)
strerror(errno)); strerror(errno));
return 0UL; return 0UL;
} }
if (delay == -1) { if (delay < 0) {
delay = 0UL; delay = 0;
} }
pts = ((uint64_t) delay * 90 * 1000) pts = ((uint64_t) (delay + RingBufferUsedBytes(OssRingBuffer)) * 90 * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample); / (AudioSampleRate * AudioChannels * AudioBytesProSample);
pts += ((uint64_t) RingBufferUsedBytes(OssRingBuffer) * 90 * 1000) Debug(4, "audio/oss: hw+sw delay %zd %" PRId64 " ms\n",
/ (AudioSampleRate * AudioChannels * AudioBytesProSample); RingBufferUsedBytes(OssRingBuffer), pts / 90);
if (pts > 600 * 90) {
Debug(4, "audio/oss: hw+sw delay %zd %" PRId64 " ms\n",
RingBufferUsedBytes(OssRingBuffer), pts / 90);
}
return pts; return pts;
} }
@@ -1687,6 +1779,7 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
int ret; int ret;
int tmp; int tmp;
int delay; int delay;
audio_buf_info bi;
if (OssPcmFildes == -1) { // OSS not ready if (OssPcmFildes == -1) { // OSS not ready
return -1; return -1;
@@ -1750,46 +1843,54 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
// FIXME: setup buffers // FIXME: setup buffers
if (1) { #ifdef SNDCTL_DSP_POLICY
audio_buf_info bi; tmp = 3;
if (ioctl(OssPcmFildes, SNDCTL_DSP_POLICY, &tmp) == -1) {
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETOSPACE, &bi) == -1) { Error(_("audio/oss: ioctl(SNDCTL_DSP_POLICY): %s\n"), strerror(errno));
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETOSPACE): %s\n"), } else {
strerror(errno)); Info("audio/oss: set policy to %d\n", tmp);
} else {
Debug(3, "audio/oss: %d bytes buffered\n", bi.bytes);
}
tmp = -1;
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETODELAY, &tmp) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETODELAY): %s\n"),
strerror(errno));
// FIXME: stop player, set setup failed flag
return -1;
}
if (tmp == -1) {
tmp = 0;
}
// start when enough bytes for initial write
OssStartThreshold = bi.bytes + tmp;
// buffer time/delay in ms
delay = AudioBufferTime;
if (VideoAudioDelay > -100) {
delay += 100 + VideoAudioDelay / 90;
}
if (OssStartThreshold <
(*freq * *channels * AudioBytesProSample * delay) / 1000U) {
OssStartThreshold =
(*freq * *channels * AudioBytesProSample * delay) / 1000U;
}
// no bigger, than the buffer
if (OssStartThreshold > RingBufferFreeBytes(OssRingBuffer)) {
OssStartThreshold = RingBufferFreeBytes(OssRingBuffer);
}
Info(_("audio/oss: delay %u ms\n"), (OssStartThreshold * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
} }
#endif
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETOSPACE): %s\n"),
strerror(errno));
bi.fragsize = 4096;
bi.fragstotal = 16;
} else {
Debug(3, "audio/oss: %d bytes buffered\n", bi.bytes);
}
OssFragmentTime = (bi.fragsize * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
Info(_("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);
// start when enough bytes for initial write
OssStartThreshold = (bi.fragsize - 1) * bi.fragstotal;
// buffer time/delay in ms
delay = AudioBufferTime + 300;
if (VideoAudioDelay > 0) {
delay += VideoAudioDelay / 90;
}
if (OssStartThreshold <
(AudioSampleRate * AudioChannels * AudioBytesProSample * delay) /
1000U) {
OssStartThreshold =
(AudioSampleRate * AudioChannels * AudioBytesProSample * delay) /
1000U;
}
// no bigger, than the buffer
if (OssStartThreshold > RingBufferFreeBytes(OssRingBuffer)) {
OssStartThreshold = RingBufferFreeBytes(OssRingBuffer);
}
Info(_("audio/oss: delay %u ms\n"), (OssStartThreshold * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
return ret; return ret;
} }
@@ -1843,13 +1944,16 @@ static const AudioModule OssModule = {
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
.Thread = OssThread, .Thread = OssThread,
.Enqueue = OssThreadEnqueue, .Enqueue = OssThreadEnqueue,
.VideoReady = OssVideoReady,
.FlushBuffers = OssThreadFlushBuffers, .FlushBuffers = OssThreadFlushBuffers,
#else #else
.Enqueue = OssEnqueue, .Enqueue = OssEnqueue,
.VideoReady = OssVideoReady,
.FlushBuffers = OssFlushBuffers, .FlushBuffers = OssFlushBuffers,
#endif #endif
.Poller = OssPoller, .Poller = OssPoller,
.FreeBytes = OssFreeBytes, .FreeBytes = OssFreeBytes,
.UsedBytes = OssUsedBytes,
.GetDelay = OssGetDelay, .GetDelay = OssGetDelay,
.SetVolume = OssSetVolume, .SetVolume = OssSetVolume,
.Setup = OssSetup, .Setup = OssSetup,
@@ -1885,6 +1989,14 @@ static int NoopFreeBytes(void)
return INT32_MAX; // no driver, much space return INT32_MAX; // no driver, much space
} }
/**
** Get used bytes in audio output.
*/
static int NoopUsedBytes(void)
{
return 0; // no driver, nothing used
}
/** /**
** Get audio delay in time stamps. ** Get audio delay in time stamps.
** **
@@ -1932,9 +2044,11 @@ static void NoopVoid(void)
static const AudioModule NoopModule = { static const AudioModule NoopModule = {
.Name = "noop", .Name = "noop",
.Enqueue = NoopEnqueue, .Enqueue = NoopEnqueue,
.VideoReady = NoopVoid,
.FlushBuffers = NoopVoid, .FlushBuffers = NoopVoid,
.Poller = NoopVoid, .Poller = NoopVoid,
.FreeBytes = NoopFreeBytes, .FreeBytes = NoopFreeBytes,
.UsedBytes = NoopUsedBytes,
.GetDelay = NoopGetDelay, .GetDelay = NoopGetDelay,
.SetVolume = NoopSetVolume, .SetVolume = NoopSetVolume,
.Setup = NoopSetup, .Setup = NoopSetup,
@@ -1964,6 +2078,10 @@ static void *AudioPlayHandlerThread(void *dummy)
pthread_cond_wait(&AudioStartCond, &AudioMutex); pthread_cond_wait(&AudioStartCond, &AudioMutex);
// cond_wait can return, without signal! // cond_wait can return, without signal!
} while (!AudioRunning); } while (!AudioRunning);
Debug(3, "audio: ----> %d ms\n", (AudioUsedBytes() * 1000)
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
pthread_mutex_unlock(&AudioMutex); pthread_mutex_unlock(&AudioMutex);
#ifdef USE_AUDIORING #ifdef USE_AUDIORING
@@ -2064,7 +2182,38 @@ static const AudioModule *AudioModules[] = {
*/ */
void AudioEnqueue(const void *samples, int count) void AudioEnqueue(const void *samples, int count)
{ {
if (0) {
static uint32_t last;
static uint32_t tick;
static uint32_t max = 101;
uint64_t delay;
delay = AudioGetDelay();
tick = GetMsTicks();
if ((last && tick - last > max) && AudioRunning) {
//max = tick - last;
Debug(3, "audio: packet delta %d %lu\n", tick - last, delay / 90);
}
last = tick;
}
AudioUsedModule->Enqueue(samples, count); AudioUsedModule->Enqueue(samples, 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 *
AudioBytesProSample);
}
}
/**
** Video is ready.
*/
void AudioVideoReady(void)
{
AudioVideoIsReady = 1;
AudioUsedModule->VideoReady();
} }
/** /**
@@ -2091,6 +2240,14 @@ int AudioFreeBytes(void)
return AudioUsedModule->FreeBytes(); return AudioUsedModule->FreeBytes();
} }
/**
** Get used bytes in audio output.
*/
int AudioUsedBytes(void)
{
return AudioUsedModule->UsedBytes();
}
/** /**
** Get audio delay in time stamps. ** Get audio delay in time stamps.
** **
@@ -2183,11 +2340,12 @@ int AudioSetup(int *freq, int *channels, int use_ac3)
void AudioPlay(void) void AudioPlay(void)
{ {
if (!AudioPaused) { if (!AudioPaused) {
Warning("audio: not paused, check the code\n"); Debug(3, "audio: not paused, check the code\n");
return; return;
} }
Debug(3, "audio: resumed\n"); Debug(3, "audio: resumed\n");
AudioPaused = 0; AudioPaused = 0;
AudioEnqueue(NULL, 0); // wakeup thread
} }
/** /**
@@ -2196,13 +2354,28 @@ void AudioPlay(void)
void AudioPause(void) void AudioPause(void)
{ {
if (AudioPaused) { if (AudioPaused) {
Warning("audio: already paused, check the code\n"); Debug(3, "audio: already paused, check the code\n");
return; return;
} }
Debug(3, "audio: paused\n"); Debug(3, "audio: paused\n");
AudioPaused = 1; AudioPaused = 1;
} }
/**
** Set audio buffer time.
**
** PES audio packets have a max distance of 300 ms.
** TS audio packet have a max distance of 100 ms.
** The period size of the audio buffer is 24 ms.
*/
void AudioSetBufferTime(int delay)
{
if (!delay) {
delay = 336;
}
AudioBufferTime = delay;
}
/** /**
** Set pcm audio device. ** Set pcm audio device.
** **

View File

@@ -31,8 +31,7 @@ extern void AudioEnqueue(const void *, int); ///< buffer audio samples
extern void AudioFlushBuffers(void); ///< flush audio buffers extern void AudioFlushBuffers(void); ///< flush audio buffers
extern void AudioPoller(void); ///< poll audio events/handling extern void AudioPoller(void); ///< poll audio events/handling
extern int AudioFreeBytes(void); ///< free bytes in audio output extern int AudioFreeBytes(void); ///< free bytes in audio output
extern int AudioUsedBytes(void); ///< used bytes in audio output
//extern int AudioUsedBytes(void); ///< used bytes in audio output
extern uint64_t AudioGetDelay(void); ///< get current audio delay extern uint64_t AudioGetDelay(void); ///< get current audio delay
extern void AudioSetClock(int64_t); ///< set audio clock base extern void AudioSetClock(int64_t); ///< set audio clock base
extern int64_t AudioGetClock(); ///< get current audio clock extern int64_t AudioGetClock(); ///< get current audio clock
@@ -42,6 +41,8 @@ extern int AudioSetup(int *, int *, int); ///< setup audio output
extern void AudioPlay(void); ///< play audio extern void AudioPlay(void); ///< play audio
extern void AudioPause(void); ///< pause audio extern void AudioPause(void); ///< pause audio
extern void AudioSetBufferTime(int); ///< set audio buffer time
extern void AudioSetDevice(const char *); ///< set PCM audio device extern void AudioSetDevice(const char *); ///< set PCM audio device
extern void AudioSetDeviceAC3(const char *); ///< set pass-through device extern void AudioSetDeviceAC3(const char *); ///< set pass-through device
extern void AudioSetChannel(const char *); ///< set mixer channel extern void AudioSetChannel(const char *); ///< set mixer channel

798
codec.c
View File

@@ -30,13 +30,10 @@
/// many bugs and incompatiblity in it. Don't use this shit. /// many bugs and incompatiblity in it. Don't use this shit.
/// ///
/** /// compile with passthrough support (stable, ac3 only)
** use av_parser to support insane dvb audio streams.
*/
#define USE_AVPARSER
/// compile with passthrough support (experimental)
#define USE_PASSTHROUGH #define USE_PASSTHROUGH
/// compile audio drift correction support (experimental)
#define noUSE_AUDIO_DRIFT_CORRECTION
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@@ -355,7 +352,7 @@ void CodecVideoOpen(VideoDecoder * decoder, const char *name, int codec_id)
{ {
AVCodec *video_codec; AVCodec *video_codec;
Debug(3, "codec: using codec %s or ID %#04x\n", name, codec_id); Debug(3, "codec: using video codec %s or ID %#06x\n", name, codec_id);
if (decoder->VideoCtx) { if (decoder->VideoCtx) {
Error(_("codec: missing close\n")); Error(_("codec: missing close\n"));
@@ -380,7 +377,7 @@ void CodecVideoOpen(VideoDecoder * decoder, const char *name, int codec_id)
if (name && (video_codec = avcodec_find_decoder_by_name(name))) { if (name && (video_codec = avcodec_find_decoder_by_name(name))) {
Debug(3, "codec: vdpau decoder found\n"); Debug(3, "codec: vdpau decoder found\n");
} else if (!(video_codec = avcodec_find_decoder(codec_id))) { } else if (!(video_codec = avcodec_find_decoder(codec_id))) {
Fatal(_("codec: codec ID %#04x not found\n"), codec_id); Fatal(_("codec: codec ID %#06x not found\n"), codec_id);
// FIXME: none fatal // FIXME: none fatal
} }
decoder->VideoCodec = video_codec; decoder->VideoCodec = video_codec;
@@ -603,8 +600,6 @@ struct _audio_decoder_
AVCodec *AudioCodec; ///< audio codec AVCodec *AudioCodec; ///< audio codec
AVCodecContext *AudioCtx; ///< audio codec context AVCodecContext *AudioCtx; ///< audio codec context
/// audio parser to support insane dvb streaks
AVCodecParserContext *AudioParser;
int PassthroughAC3; ///< current ac-3 pass-through int PassthroughAC3; ///< current ac-3 pass-through
int SampleRate; ///< current stream sample rate int SampleRate; ///< current stream sample rate
int Channels; ///< current stream channels int Channels; ///< current stream channels
@@ -614,6 +609,21 @@ struct _audio_decoder_
ReSampleContext *ReSample; ///< audio resampling context ReSampleContext *ReSample; ///< audio resampling context
int64_t LastDelay; ///< last delay
struct timespec LastTime; ///< last time
int64_t LastPTS; ///< last PTS
int Drift; ///< accumulated audio drift
int DriftCorr; ///< audio drift correction value
int DriftFrac; ///< audio drift fraction for ac3
struct AVResampleContext *AvResample; ///< second audio resample context
#define MAX_CHANNELS 8 ///< max number of channels supported
int16_t *Buffer[MAX_CHANNELS]; ///< deinterleave sample buffers
int BufferSize; ///< size of sample buffer
int16_t *Remain[MAX_CHANNELS]; ///< filter remaining samples
int RemainSize; ///< size of remain buffer
int RemainCount; ///< number of remaining samples
}; };
#ifdef USE_PASSTHROUGH #ifdef USE_PASSTHROUGH
@@ -626,6 +636,7 @@ static char CodecPassthroughAC3; ///< pass ac3 through
static const int CodecPassthroughAC3 = 0; static const int CodecPassthroughAC3 = 0;
#endif #endif
static char CodecDownmix; ///< enable ac-3 downmix
/** /**
** Allocate a new audio decoder context. ** Allocate a new audio decoder context.
@@ -665,10 +676,12 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
{ {
AVCodec *audio_codec; AVCodec *audio_codec;
Debug(3, "codec: using audio codec %s or ID %#06x\n", name, codec_id);
if (name && (audio_codec = avcodec_find_decoder_by_name(name))) { if (name && (audio_codec = avcodec_find_decoder_by_name(name))) {
Debug(3, "codec: audio decoder '%s' found\n", name); Debug(3, "codec: audio decoder '%s' found\n", name);
} else if (!(audio_codec = avcodec_find_decoder(codec_id))) { } else if (!(audio_codec = avcodec_find_decoder(codec_id))) {
Fatal(_("codec: codec ID %#04x not found\n"), codec_id); Fatal(_("codec: codec ID %#06x not found\n"), codec_id);
// FIXME: errors aren't fatal // FIXME: errors aren't fatal
} }
audio_decoder->AudioCodec = audio_codec; audio_decoder->AudioCodec = audio_codec;
@@ -676,6 +689,12 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
if (!(audio_decoder->AudioCtx = avcodec_alloc_context3(audio_codec))) { if (!(audio_decoder->AudioCtx = avcodec_alloc_context3(audio_codec))) {
Fatal(_("codec: can't allocate audio codec context\n")); Fatal(_("codec: can't allocate audio codec context\n"));
} }
if (CodecDownmix) {
audio_decoder->AudioCtx->request_channels = 2;
audio_decoder->AudioCtx->request_channel_layout =
AV_CH_LAYOUT_STEREO_DOWNMIX;
}
pthread_mutex_lock(&CodecLockMutex); pthread_mutex_lock(&CodecLockMutex);
// open codec // open codec
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(53,5,0) #if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(53,5,0)
@@ -684,9 +703,19 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
Fatal(_("codec: can't open audio codec\n")); Fatal(_("codec: can't open audio codec\n"));
} }
#else #else
if (avcodec_open2(audio_decoder->AudioCtx, audio_codec, NULL) < 0) { if (1) {
pthread_mutex_unlock(&CodecLockMutex); AVDictionary *av_dict;
Fatal(_("codec: can't open audio codec\n"));
av_dict = NULL;
// FIXME: import settings
//av_dict_set(&av_dict, "dmix_mode", "0", 0);
//av_dict_set(&av_dict, "ltrt_cmixlev", "1.414", 0);
//av_dict_set(&av_dict, "loro_cmixlev", "1.414", 0);
if (avcodec_open2(audio_decoder->AudioCtx, audio_codec, &av_dict) < 0) {
pthread_mutex_unlock(&CodecLockMutex);
Fatal(_("codec: can't open audio codec\n"));
}
av_dict_free(&av_dict);
} }
#endif #endif
pthread_mutex_unlock(&CodecLockMutex); pthread_mutex_unlock(&CodecLockMutex);
@@ -697,14 +726,11 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
// we do not send complete frames // we do not send complete frames
audio_decoder->AudioCtx->flags |= CODEC_FLAG_TRUNCATED; audio_decoder->AudioCtx->flags |= CODEC_FLAG_TRUNCATED;
} }
if (!(audio_decoder->AudioParser =
av_parser_init(audio_decoder->AudioCtx->codec_id))) {
Fatal(_("codec: can't init audio parser\n"));
}
audio_decoder->SampleRate = 0; audio_decoder->SampleRate = 0;
audio_decoder->Channels = 0; audio_decoder->Channels = 0;
audio_decoder->HwSampleRate = 0; audio_decoder->HwSampleRate = 0;
audio_decoder->HwChannels = 0; audio_decoder->HwChannels = 0;
audio_decoder->LastDelay = 0;
} }
/** /**
@@ -715,14 +741,25 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
void CodecAudioClose(AudioDecoder * audio_decoder) void CodecAudioClose(AudioDecoder * audio_decoder)
{ {
// FIXME: output any buffered data // FIXME: output any buffered data
if (audio_decoder->AvResample) {
int ch;
av_resample_close(audio_decoder->AvResample);
audio_decoder->AvResample = NULL;
audio_decoder->RemainCount = 0;
audio_decoder->BufferSize = 0;
audio_decoder->RemainSize = 0;
for (ch = 0; ch < MAX_CHANNELS; ++ch) {
free(audio_decoder->Buffer[ch]);
audio_decoder->Buffer[ch] = NULL;
free(audio_decoder->Remain[ch]);
audio_decoder->Remain[ch] = NULL;
}
}
if (audio_decoder->ReSample) { if (audio_decoder->ReSample) {
audio_resample_close(audio_decoder->ReSample); audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL; audio_decoder->ReSample = NULL;
} }
if (audio_decoder->AudioParser) {
av_parser_close(audio_decoder->AudioParser);
audio_decoder->AudioParser = NULL;
}
if (audio_decoder->AudioCtx) { if (audio_decoder->AudioCtx) {
pthread_mutex_lock(&CodecLockMutex); pthread_mutex_lock(&CodecLockMutex);
avcodec_close(audio_decoder->AudioCtx); avcodec_close(audio_decoder->AudioCtx);
@@ -742,6 +779,16 @@ void CodecSetAudioPassthrough(int mask)
(void)mask; (void)mask;
} }
/**
** Set audio downmix.
**
** @param onoff enable/disable downmix.
*/
void CodecSetAudioDownmix(int onoff)
{
CodecDownmix = onoff;
}
/** /**
** Reorder audio frame. ** Reorder audio frame.
** **
@@ -798,7 +845,270 @@ static void CodecReorderAudioFrame(int16_t * buf, int size, int channels)
} }
} }
#ifdef USE_AVPARSER /**
** Set/update audio pts clock.
**
** @param audio_decoder audio decoder data
** @param pts presentation timestamp
*/
static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
{
struct timespec nowtime;
int64_t delay;
int64_t tim_diff;
int64_t pts_diff;
int drift;
int corr;
AudioSetClock(pts);
delay = AudioGetDelay();
if (!delay) {
return;
}
clock_gettime(CLOCK_REALTIME, &nowtime);
if (!audio_decoder->LastDelay) {
audio_decoder->LastTime = nowtime;
audio_decoder->LastPTS = pts;
audio_decoder->LastDelay = delay;
audio_decoder->Drift = 0;
audio_decoder->DriftFrac = 0;
Debug(3, "codec/audio: inital delay %zd ms\n", delay / 90);
return;
}
// collect over some time
pts_diff = pts - audio_decoder->LastPTS;
if (pts_diff < 10 * 1000 * 90) {
return;
}
tim_diff = (nowtime.tv_sec - audio_decoder->LastTime.tv_sec)
* 1000 * 1000 * 1000 + (nowtime.tv_nsec -
audio_decoder->LastTime.tv_nsec);
drift =
(tim_diff * 90) / (1000 * 1000) - pts_diff + delay -
audio_decoder->LastDelay;
// adjust rounding error
nowtime.tv_nsec -= nowtime.tv_nsec % (1000 * 1000 / 90);
audio_decoder->LastTime = nowtime;
audio_decoder->LastPTS = pts;
audio_decoder->LastDelay = delay;
if (0) {
Debug(3, "codec/audio: interval P:%5zdms T:%5zdms D:%4zdms %f %d\n",
pts_diff / 90, tim_diff / (1000 * 1000), delay / 90, drift / 90.0,
audio_decoder->DriftCorr);
}
// underruns and av_resample have the same time :(((
if (abs(drift) > 10 * 90) {
// drift too big, pts changed?
Debug(3, "codec/audio: drift(%6d) %3dms reset\n",
audio_decoder->DriftCorr, drift / 90);
audio_decoder->LastDelay = 0;
} else {
drift += audio_decoder->Drift;
audio_decoder->Drift = drift;
corr = (10 * audio_decoder->HwSampleRate * drift) / (90 * 1000);
#if defined(USE_PASSTHROUGH) && !defined(USE_AC3_DRIFT_CORRECTION)
// SPDIF/HDMI passthrough
if (!CodecPassthroughAC3
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_AC3)
#endif
{
audio_decoder->DriftCorr = -corr;
}
if (audio_decoder->DriftCorr < -20000) { // limit correction
audio_decoder->DriftCorr = -20000;
} else if (audio_decoder->DriftCorr > 20000) {
audio_decoder->DriftCorr = 20000;
}
}
// FIXME: this works with libav 0.8, and only with >10ms with ffmpeg 0.10
if (audio_decoder->AvResample && audio_decoder->DriftCorr) {
int distance;
distance = (pts_diff * audio_decoder->HwSampleRate) / (90 * 1000);
av_resample_compensate(audio_decoder->AvResample,
audio_decoder->DriftCorr / 10, distance);
}
Debug(3, "codec/audio: drift(%6d) %8dus %5d\n", audio_decoder->DriftCorr,
drift * 1000 / 90, corr);
}
/**
** Handle audio format changes.
**
** @param audio_decoder audio decoder data
*/
static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
{
const AVCodecContext *audio_ctx;
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);
audio_decoder->ReSample = NULL;
}
if (audio_decoder->AvResample) {
av_resample_close(audio_decoder->AvResample);
audio_decoder->AvResample = NULL;
audio_decoder->RemainCount = 0;
}
audio_decoder->SampleRate = audio_ctx->sample_rate;
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
audio_decoder->Channels = audio_ctx->channels;
// SPDIF/HDMI passthrough
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
audio_decoder->HwChannels = 2;
isAC3 = 1;
} else {
audio_decoder->HwChannels = audio_ctx->channels;
isAC3 = 0;
}
// channels not support?
if ((err =
AudioSetup(&audio_decoder->HwSampleRate,
&audio_decoder->HwChannels, isAC3))) {
Debug(3, "codec/audio: resample %dHz *%d -> %dHz *%d\n",
audio_ctx->sample_rate, audio_ctx->channels,
audio_decoder->HwSampleRate, audio_decoder->HwChannels);
if (err == 1) {
audio_decoder->ReSample =
av_audio_resample_init(audio_decoder->HwChannels,
audio_ctx->channels, audio_decoder->HwSampleRate,
audio_ctx->sample_rate, audio_ctx->sample_fmt,
audio_ctx->sample_fmt, 16, 10, 0, 0.8);
// libav-0.8_pre didn't support 6 -> 2 channels
if (!audio_decoder->ReSample) {
Error(_("codec/audio: resample setup error\n"));
audio_decoder->HwChannels = 0;
audio_decoder->HwSampleRate = 0;
return;
}
} else {
Debug(3, "codec/audio: audio setup error\n");
// FIXME: handle errors
audio_decoder->HwChannels = 0;
audio_decoder->HwSampleRate = 0;
return;
}
}
// prepare audio drift resample
#ifdef USE_AUDIO_DRIFT_CORRECTION
if (!isAC3) {
if (audio_decoder->AvResample) {
Error(_("codec/audio: overwrite resample\n"));
}
audio_decoder->AvResample =
av_resample_init(audio_decoder->HwSampleRate,
audio_decoder->HwSampleRate, 16, 10, 0, 0.8);
if (!audio_decoder->AvResample) {
Error(_("codec/audio: AvResample setup error\n"));
} else {
// reset drift to some default value
audio_decoder->DriftCorr /= 2;
audio_decoder->DriftFrac = 0;
av_resample_compensate(audio_decoder->AvResample,
audio_decoder->DriftCorr / 10,
10 * audio_decoder->HwSampleRate);
}
}
#endif
}
/**
** Codec enqueue audio samples.
**
** @param audio_decoder audio decoder data
** @param data samples data
** @param count number of samples
**
*/
void CodecAudioEnqueue(AudioDecoder * audio_decoder, int16_t * data, int count)
{
#ifdef USE_AUDIO_DRIFT_CORRECTION
if (audio_decoder->AvResample) {
int16_t buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
int16_t buftmp[MAX_CHANNELS][(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4];
int consumed;
int i;
int n;
int ch;
int bytes_n;
bytes_n = count / audio_decoder->HwChannels;
// resize sample buffer, if needed
if (audio_decoder->RemainCount + bytes_n > audio_decoder->BufferSize) {
audio_decoder->BufferSize = audio_decoder->RemainCount + bytes_n;
for (ch = 0; ch < MAX_CHANNELS; ++ch) {
audio_decoder->Buffer[ch] =
realloc(audio_decoder->Buffer[ch],
audio_decoder->BufferSize);
}
}
// copy remaining bytes into sample buffer
for (ch = 0; ch < audio_decoder->HwChannels; ++ch) {
memcpy(audio_decoder->Buffer[ch], audio_decoder->Remain[ch],
audio_decoder->RemainCount);
}
// deinterleave samples into sample buffer
for (i = 0; i < bytes_n / 2; i++) {
for (ch = 0; ch < audio_decoder->HwChannels; ++ch) {
audio_decoder->Buffer[ch][audio_decoder->RemainCount / 2 + i]
= data[i * audio_decoder->HwChannels + ch];
}
}
bytes_n += audio_decoder->RemainSize;
n = 0; // keep gcc lucky
// resample the sample buffer into tmp buffer
for (ch = 0; ch < audio_decoder->HwChannels; ++ch) {
n = av_resample(audio_decoder->AvResample, buftmp[ch],
audio_decoder->Buffer[ch], &consumed, bytes_n / 2,
sizeof(buftmp[ch]) / 2, ch == audio_decoder->HwChannels - 1);
// fixme remaining channels
if (bytes_n - consumed * 2 > audio_decoder->RemainSize) {
audio_decoder->RemainSize = bytes_n - consumed * 2;
}
audio_decoder->Remain[ch] =
realloc(audio_decoder->Remain[ch], audio_decoder->RemainSize);
memcpy(audio_decoder->Remain[ch],
audio_decoder->Buffer[ch] + consumed,
audio_decoder->RemainSize);
audio_decoder->RemainCount = audio_decoder->RemainSize;
}
// interleave samples from sample buffer
for (i = 0; i < n; i++) {
for (ch = 0; ch < audio_decoder->HwChannels; ++ch) {
buf[i * audio_decoder->HwChannels + ch] = buftmp[ch][i];
}
}
n *= 2;
n *= audio_decoder->HwChannels;
CodecReorderAudioFrame(buf, n, audio_decoder->HwChannels);
AudioEnqueue(buf, n);
return;
}
#endif
CodecReorderAudioFrame(data, count, audio_decoder->HwChannels);
AudioEnqueue(data, count);
}
/** /**
** Decode an audio packet. ** Decode an audio packet.
@@ -812,324 +1122,173 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
{ {
int16_t buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 + int16_t buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16))); FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
int buf_sz;
int l;
AVCodecContext *audio_ctx; AVCodecContext *audio_ctx;
int index;
//#define spkt avpkt
#if 1 // didn't fix crash in av_parser_parse2
AVPacket spkt[1];
// av_new_packet reserves FF_INPUT_BUFFER_PADDING_SIZE and clears it
if (av_new_packet(spkt, avpkt->size)) {
Error(_("codec: out of memory\n"));
return;
}
memcpy(spkt->data, avpkt->data, avpkt->size);
spkt->pts = avpkt->pts;
spkt->dts = avpkt->dts;
#endif
#ifdef DEBUG
if (!audio_decoder->AudioParser) {
Fatal(_("codec: internal error parser freeded while running\n"));
}
#endif
audio_ctx = audio_decoder->AudioCtx; audio_ctx = audio_decoder->AudioCtx;
index = 0;
while (spkt->size > index) {
int n;
int l;
AVPacket dpkt[1];
av_init_packet(dpkt); buf_sz = sizeof(buf);
n = av_parser_parse2(audio_decoder->AudioParser, audio_ctx, l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, (AVPacket *) avpkt);
&dpkt->data, &dpkt->size, spkt->data + index, spkt->size - index, if (avpkt->size != l) {
!index ? (uint64_t) spkt->pts : AV_NOPTS_VALUE, if (l == AVERROR(EAGAIN)) {
!index ? (uint64_t) spkt->dts : AV_NOPTS_VALUE, -1); Error(_("codec: latm\n"));
return;
// FIXME: make this a function for both #ifdef cases }
if (dpkt->size) { if (l < 0) { // no audio frame could be decompressed
int buf_sz; Error(_("codec: error audio data\n"));
return;
dpkt->pts = audio_decoder->AudioParser->pts; }
dpkt->dts = audio_decoder->AudioParser->dts; Error(_("codec: error more than one frame data\n"));
buf_sz = sizeof(buf); }
l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt);
if (l == AVERROR(EAGAIN)) {
index += n; // this is needed for aac latm
continue;
}
if (l < 0) { // no audio frame could be decompressed
Error(_("codec: error audio data at %d\n"), index);
break;
}
#ifdef notyetFF_API_OLD_DECODE_AUDIO #ifdef notyetFF_API_OLD_DECODE_AUDIO
// FIXME: ffmpeg git comeing // FIXME: ffmpeg git comeing
int got_frame; int got_frame;
avcodec_decode_audio4(audio_ctx, frame, &got_frame, dpkt); avcodec_decode_audio4(audio_ctx, frame, &got_frame, avpkt);
#else #else
#endif #endif
// Update audio clock
if ((uint64_t) dpkt->pts != AV_NOPTS_VALUE) {
AudioSetClock(dpkt->pts);
}
// FIXME: must first play remainings bytes, than change and play new.
if (audio_decoder->PassthroughAC3 != CodecPassthroughAC3
|| audio_decoder->SampleRate != audio_ctx->sample_rate
|| audio_decoder->Channels != audio_ctx->channels) {
int err;
int isAC3;
audio_decoder->PassthroughAC3 = CodecPassthroughAC3; // update audio clock
// FIXME: use swr_convert from swresample (only in ffmpeg!) if (avpkt->pts != (int64_t) AV_NOPTS_VALUE) {
// FIXME: tell ac3 decoder to use downmix CodecAudioSetClock(audio_decoder, avpkt->pts);
if (audio_decoder->ReSample) {
audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL;
}
audio_decoder->SampleRate = audio_ctx->sample_rate; }
audio_decoder->HwSampleRate = audio_ctx->sample_rate; // FIXME: must first play remainings bytes, than change and play new.
audio_decoder->Channels = audio_ctx->channels; if (audio_decoder->PassthroughAC3 != CodecPassthroughAC3
// SPDIF/HDMI passthrough || audio_decoder->SampleRate != audio_ctx->sample_rate
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) { || audio_decoder->Channels != audio_ctx->channels) {
audio_decoder->HwChannels = 2; CodecAudioUpdateFormat(audio_decoder);
isAC3 = 1; }
} else {
audio_decoder->HwChannels = audio_ctx->channels;
isAC3 = 0;
}
// channels not support? if (audio_decoder->HwSampleRate && audio_decoder->HwChannels) {
if ((err = // need to resample audio
AudioSetup(&audio_decoder->HwSampleRate, if (audio_decoder->ReSample) {
&audio_decoder->HwChannels, isAC3))) { int16_t outbuf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
Debug(3, "codec/audio: resample %dHz *%d -> %dHz *%d\n", FF_INPUT_BUFFER_PADDING_SIZE]
audio_ctx->sample_rate, audio_ctx->channels, __attribute__ ((aligned(16)));
audio_decoder->HwSampleRate, int outlen;
audio_decoder->HwChannels);
if (err == 1) { // FIXME: libav-0.7.2 crash here
audio_decoder->ReSample = outlen =
av_audio_resample_init(audio_decoder->HwChannels, audio_resample(audio_decoder->ReSample, outbuf, buf, buf_sz);
audio_ctx->channels, audio_decoder->HwSampleRate,
audio_ctx->sample_rate, audio_ctx->sample_fmt,
audio_ctx->sample_fmt, 16, 10, 0, 0.8);
// libav-0.8_pre didn't support 6 -> 2 channels
if (!audio_decoder->ReSample) {
Error(_("codec/audio: resample setup error\n"));
audio_decoder->HwChannels = 0;
audio_decoder->HwSampleRate = 0;
}
} else {
Debug(3, "codec/audio: audio setup error\n");
// FIXME: handle errors
audio_decoder->HwChannels = 0;
audio_decoder->HwSampleRate = 0;
break;
}
}
}
if (audio_decoder->HwSampleRate && audio_decoder->HwChannels) {
// need to resample audio
if (audio_decoder->ReSample) {
int16_t outbuf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE]
__attribute__ ((aligned(16)));
int outlen;
// FIXME: libav-0.7.2 crash here
outlen =
audio_resample(audio_decoder->ReSample, outbuf, buf,
buf_sz);
#ifdef DEBUG #ifdef DEBUG
if (outlen != buf_sz) { if (outlen != buf_sz) {
Debug(3, "codec/audio: possible fixed ffmpeg\n"); Debug(3, "codec/audio: possible fixed ffmpeg\n");
} }
#endif #endif
if (outlen) { if (outlen) {
// outlen seems to be wrong in ffmpeg-0.9 // outlen seems to be wrong in ffmpeg-0.9
outlen /= audio_decoder->Channels * outlen /= audio_decoder->Channels *
av_get_bytes_per_sample(audio_ctx->sample_fmt); av_get_bytes_per_sample(audio_ctx->sample_fmt);
outlen *= outlen *=
audio_decoder->HwChannels * audio_decoder->HwChannels *
av_get_bytes_per_sample(audio_ctx->sample_fmt); av_get_bytes_per_sample(audio_ctx->sample_fmt);
Debug(4, "codec/audio: %d -> %d\n", buf_sz, outlen); Debug(4, "codec/audio: %d -> %d\n", buf_sz, outlen);
CodecReorderAudioFrame(outbuf, outlen, CodecAudioEnqueue(audio_decoder, outbuf, outlen);
audio_decoder->HwChannels); }
AudioEnqueue(outbuf, outlen); } else {
}
} else {
#ifdef USE_PASSTHROUGH #ifdef USE_PASSTHROUGH
// SPDIF/HDMI passthrough // SPDIF/HDMI passthrough
if (CodecPassthroughAC3 if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
&& audio_ctx->codec_id == CODEC_ID_AC3) { // build SPDIF header and append A52 audio to it
// build SPDIF header and append A52 audio to it // avpkt is the original data
// dpkt is the original data buf_sz = 6144;
buf_sz = 6144;
if (buf_sz < dpkt->size + 8) { #ifdef USE_AC3_DRIFT_CORRECTION
Error(_ if (1) {
("codec/audio: decoded data smaller than encoded\n")); int x;
break;
} x = (audio_decoder->DriftFrac +
// copy original data for output (audio_decoder->DriftCorr * buf_sz)) / (10 *
// FIXME: not 100% sure, if endian is correct audio_decoder->HwSampleRate * 100);
buf[0] = htole16(0xF872); // iec 61937 sync word audio_decoder->DriftFrac =
buf[1] = htole16(0x4E1F); (audio_decoder->DriftFrac +
buf[2] = htole16(0x01 | (dpkt->data[5] & 0x07) << 8); (audio_decoder->DriftCorr * buf_sz)) % (10 *
buf[3] = htole16(dpkt->size * 8); audio_decoder->HwSampleRate * 100);
swab(dpkt->data, buf + 4, dpkt->size); x *= audio_decoder->HwChannels * 4;
memset(buf + 4 + dpkt->size / 2, 0, if (x < -64) { // limit correction
buf_sz - 8 - dpkt->size); x = -64;
} else if (x > 64) {
x = 64;
} }
#if 0 buf_sz += x;
//
// old experimental code
//
if (1) {
// FIXME: need to detect dts
// copy original data for output
// FIXME: buf is sint
buf[0] = 0x72;
buf[1] = 0xF8;
buf[2] = 0x1F;
buf[3] = 0x4E;
buf[4] = 0x00;
switch (dpkt->size) {
case 512:
buf[5] = 0x0B;
break;
case 1024:
buf[5] = 0x0C;
break;
case 2048:
buf[5] = 0x0D;
break;
default:
Debug(3,
"codec/audio: dts sample burst not supported\n");
buf[5] = 0x00;
break;
}
buf[6] = (dpkt->size * 8);
buf[7] = (dpkt->size * 8) >> 8;
//buf[8] = 0x0B;
//buf[9] = 0x77;
//printf("%x %x\n", dpkt->data[0],dpkt->data[1]);
// swab?
memcpy(buf + 8, dpkt->data, dpkt->size);
memset(buf + 8 + dpkt->size, 0,
buf_sz - 8 - dpkt->size);
} else if (1) {
// FIXME: need to detect mp2
// FIXME: mp2 passthrough
// see softhddev.c version/layer
// 0x04 mpeg1 layer1
// 0x05 mpeg1 layer23
// 0x06 mpeg2 ext
// 0x07 mpeg2.5 layer 1
// 0x08 mpeg2.5 layer 2
// 0x09 mpeg2.5 layer 3
}
// DTS HD?
// True HD?
#endif
#endif
CodecReorderAudioFrame(buf, buf_sz,
audio_decoder->HwChannels);
AudioEnqueue(buf, buf_sz);
} }
#endif
if (buf_sz < avpkt->size + 8) {
Error(_
("codec/audio: decoded data smaller than encoded\n"));
return;
}
// copy original data for output
// FIXME: not 100% sure, if endian is correct
buf[0] = htole16(0xF872); // iec 61937 sync word
buf[1] = htole16(0x4E1F);
buf[2] = htole16(0x01 | (avpkt->data[5] & 0x07) << 8);
buf[3] = htole16(avpkt->size * 8);
swab(avpkt->data, buf + 4, avpkt->size);
memset(buf + 4 + avpkt->size / 2, 0, buf_sz - 8 - avpkt->size);
// don't play with the ac-3 samples
AudioEnqueue(buf, buf_sz);
return;
} }
#if 0
if (dpkt->size > l) { //
Error(_("codec: error more than one frame data\n")); // old experimental code
//
if (1) {
// FIXME: need to detect dts
// copy original data for output
// FIXME: buf is sint
buf[0] = 0x72;
buf[1] = 0xF8;
buf[2] = 0x1F;
buf[3] = 0x4E;
buf[4] = 0x00;
switch (avpkt->size) {
case 512:
buf[5] = 0x0B;
break;
case 1024:
buf[5] = 0x0C;
break;
case 2048:
buf[5] = 0x0D;
break;
default:
Debug(3,
"codec/audio: dts sample burst not supported\n");
buf[5] = 0x00;
break;
}
buf[6] = (avpkt->size * 8);
buf[7] = (avpkt->size * 8) >> 8;
//buf[8] = 0x0B;
//buf[9] = 0x77;
//printf("%x %x\n", avpkt->data[0],avpkt->data[1]);
// swab?
memcpy(buf + 8, avpkt->data, avpkt->size);
memset(buf + 8 + avpkt->size, 0, buf_sz - 8 - avpkt->size);
} else if (1) {
// FIXME: need to detect mp2
// FIXME: mp2 passthrough
// see softhddev.c version/layer
// 0x04 mpeg1 layer1
// 0x05 mpeg1 layer23
// 0x06 mpeg2 ext
// 0x07 mpeg2.5 layer 1
// 0x08 mpeg2.5 layer 2
// 0x09 mpeg2.5 layer 3
} }
} // DTS HD?
// True HD?
index += n;
}
#if 1
// or av_free_packet, make no difference here
av_destruct_packet(spkt);
#endif #endif
#endif
CodecAudioEnqueue(audio_decoder, buf, buf_sz);
}
}
} }
#else
/**
** Decode an audio packet.
**
** PTS must be handled self.
**
** @param audio_decoder audio decoder data
** @param avpkt audio packet
*/
void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
{
int16_t buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
AVCodecContext *audio_ctx;
int index;
//#define spkt avpkt
#if 1
AVPacket spkt[1];
// av_new_packet reserves FF_INPUT_BUFFER_PADDING_SIZE and clears it
if (av_new_packet(spkt, avpkt->size)) {
Error(_("codec: out of memory\n"));
return;
}
memcpy(spkt->data, avpkt->data, avpkt->size);
spkt->pts = avpkt->pts;
spkt->dts = avpkt->dts;
#endif
audio_ctx = audio_decoder->AudioCtx;
index = 0;
while (spkt->size > index) {
int n;
int buf_sz;
AVPacket dpkt[1];
av_init_packet(dpkt);
dpkt->data = spkt->data + index;
dpkt->size = spkt->size - index;
buf_sz = sizeof(buf);
n = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt);
if (n < 0) { // no audio frame could be decompressed
Error(_("codec: error audio data at %d\n"), index);
break;
}
#ifdef DEBUG
Debug(4, "codec/audio: -> %d\n", buf_sz);
if ((unsigned)buf_sz > sizeof(buf)) {
abort();
}
#endif
#ifdef notyetFF_API_OLD_DECODE_AUDIO
// FIXME: ffmpeg git comeing
int got_frame;
avcodec_decode_audio4(audio_ctx, frame, &got_frame, dpkt);
#else
#endif
// FIXME: see above, old code removed
index += n;
}
#if 1
// or av_free_packet, make no difference here
av_destruct_packet(spkt);
#endif
}
#endif
/** /**
** Flush the audio decoder. ** Flush the audio decoder.
** **
@@ -1137,7 +1296,6 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
*/ */
void CodecAudioFlushBuffers(AudioDecoder * decoder) void CodecAudioFlushBuffers(AudioDecoder * decoder)
{ {
// FIXME: reset audio parser
avcodec_flush_buffers(decoder->AudioCtx); avcodec_flush_buffers(decoder->AudioCtx);
} }

View File

@@ -67,6 +67,9 @@ extern void CodecAudioOpen(AudioDecoder *, const char *, int);
/// Close audio codec. /// Close audio codec.
extern void CodecAudioClose(AudioDecoder *); extern void CodecAudioClose(AudioDecoder *);
/// Decode an audio packet.
extern void CodecAudioDecodeOld(AudioDecoder *, const AVPacket *);
/// Decode an audio packet. /// Decode an audio packet.
extern void CodecAudioDecode(AudioDecoder *, const AVPacket *); extern void CodecAudioDecode(AudioDecoder *, const AVPacket *);

File diff suppressed because it is too large Load Diff

View File

@@ -37,8 +37,8 @@ extern "C"
/// C plugin play audio packet /// C plugin play audio packet
extern int PlayAudio(const uint8_t *, int, uint8_t); extern int PlayAudio(const uint8_t *, int, uint8_t);
/// C plugin mute audio /// C plugin play TS audio packet
extern void Mute(void); extern int PlayTsAudio(const uint8_t *, int);
/// C plugin set audio volume /// C plugin set audio volume
extern void SetVolumeDevice(int); extern void SetVolumeDevice(int);
@@ -50,13 +50,17 @@ extern "C"
extern uint8_t *GrabImage(int *, int, int, int, int); extern uint8_t *GrabImage(int *, int, int, int, int);
/// C plugin set play mode /// C plugin set play mode
extern void SetPlayMode(void); extern int SetPlayMode(int);
/// C plugin set trick speed
extern void TrickSpeed(int);
/// C plugin clears all video and audio data from the device /// C plugin clears all video and audio data from the device
extern void Clear(void); extern void Clear(void);
/// C plugin sets the device into play mode /// C plugin sets the device into play mode
extern void Play(void); extern void Play(void);
/// C plugin sets the device into "freeze frame" mode /// C plugin sets the device into "freeze frame" mode
extern void Freeze(void); extern void Freeze(void);
/// C plugin mute audio
extern void Mute(void);
/// C plugin display I-frame as a still picture. /// C plugin display I-frame as a still picture.
extern void StillPicture(const uint8_t *, int); extern void StillPicture(const uint8_t *, int);
/// C plugin poll if ready /// C plugin poll if ready
@@ -72,7 +76,7 @@ extern "C"
/// C plugin exit + cleanup /// C plugin exit + cleanup
extern void SoftHdDeviceExit(void); extern void SoftHdDeviceExit(void);
/// C plugin start code /// C plugin start code
extern void Start(void); extern int Start(void);
/// C plugin stop code /// C plugin stop code
extern void Stop(void); extern void Stop(void);
/// C plugin main thread hook /// C plugin main thread hook

View File

@@ -38,15 +38,20 @@ extern "C"
#include "video.h" #include "video.h"
extern void AudioPoller(void); extern void AudioPoller(void);
extern void CodecSetAudioPassthrough(int); extern void CodecSetAudioPassthrough(int);
extern void CodecSetAudioDownmix(int);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
static const char *const VERSION = "0.4.8"; static const char *const VERSION = "0.4.9"
#ifdef GIT_REV
"-GIT" GIT_REV
#endif
;
static const char *const DESCRIPTION = static const char *const DESCRIPTION =
trNOOP("A software and GPU emulated HD device"); trNOOP("A software and GPU emulated HD device");
static const char *MAINMENUENTRY = trNOOP("Suspend Soft-HD-Device"); static const char *MAINMENUENTRY = trNOOP("SoftHdDevice");
static class cSoftHdDevice *MyDevice; static class cSoftHdDevice *MyDevice;
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@@ -61,8 +66,10 @@ static const char *const Resolution[RESOLUTIONS] = {
static char ConfigMakePrimary; ///< config primary wanted static char ConfigMakePrimary; ///< config primary wanted
static char ConfigHideMainMenuEntry; ///< config hide main menu entry 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 ConfigVideoSkipLines; ///< config skip lines top/bottom
static int ConfigVideoStudioLevels; ///< config use studio levels static int ConfigVideoStudioLevels; ///< config use studio levels
static int ConfigVideo60HzMode; ///< config use 60Hz display mode
/// config deinterlace /// config deinterlace
static int ConfigVideoDeinterlace[RESOLUTIONS]; static int ConfigVideoDeinterlace[RESOLUTIONS];
@@ -84,6 +91,7 @@ static int ConfigVideoScaling[RESOLUTIONS];
static int ConfigVideoAudioDelay; ///< config audio delay static int ConfigVideoAudioDelay; ///< config audio delay
static int ConfigAudioPassthrough; ///< config audio pass-through static int ConfigAudioPassthrough; ///< config audio pass-through
static int ConfigAudioDownmix; ///< config audio downmix
static int ConfigAutoCropInterval; ///< auto crop detection interval static int ConfigAutoCropInterval; ///< auto crop detection interval
static int ConfigAutoCropDelay; ///< auto crop detection delay static int ConfigAutoCropDelay; ///< auto crop detection delay
@@ -94,6 +102,11 @@ static char ConfigSuspendX11; ///< suspend should stop x11
static volatile char DoMakePrimary; ///< flag switch primary static volatile char DoMakePrimary; ///< flag switch primary
#define SUSPEND_EXTERNAL -1 ///< play external suspend mode
#define SUSPEND_NORMAL 0 ///< normal suspend mode
#define SUSPEND_DETACHED 1 ///< detached suspend mode
static char SuspendMode; ///< suspend mode
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@@ -137,19 +150,26 @@ extern "C" void FeedKeyPress(const char *keymap, const char *key, int repeat,
} }
//dsyslog("[softhddev]%s %s, %s\n", __FUNCTION__, keymap, key); //dsyslog("[softhddev]%s %s, %s\n", __FUNCTION__, keymap, key);
csoft->Put(key, repeat, release); if (key[1]) { // no single character
csoft->Put(key, repeat, release);
} else if (!csoft->Put(key, repeat, release)) {
cRemote::Put(KBDKEY(key[0])); // feed it for edit mode
}
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// OSD // OSD
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/**
** Soft device plugin OSD class.
*/
class cSoftOsd:public cOsd class cSoftOsd:public cOsd
{ {
int Level; ///< level: subtitle //int Level; ///< level: subtitle
public: public:
cSoftOsd(int, int, uint); cSoftOsd(int, int, uint);
virtual ~ cSoftOsd(void); virtual ~ cSoftOsd(void);
virtual void Flush(void); virtual void Flush(void);
virtual void SetActive(bool); virtual void SetActive(bool);
@@ -167,7 +187,7 @@ static volatile char OsdDirty; ///< flag force redraw everything
*/ */
void cSoftOsd::SetActive(bool on) void cSoftOsd::SetActive(bool on)
{ {
dsyslog("[softhddev]%s: %d\n", __FUNCTION__, on); //dsyslog("[softhddev]%s: %d\n", __FUNCTION__, on);
if (Active() == on) { if (Active() == on) {
return; // already active, no action return; // already active, no action
@@ -188,7 +208,7 @@ cSoftOsd::cSoftOsd(int left, int top, uint level)
OsdHeight(), left, top, level); OsdHeight(), left, top, level);
*/ */
this->Level = level; //this->Level = level;
SetActive(true); SetActive(true);
} }
@@ -285,7 +305,7 @@ void cSoftOsd::Flush(void)
} }
#ifdef DEBUG #ifdef DEBUG
if (w > bitmap->Width() || h > bitmap->Height()) { if (w > bitmap->Width() || h > bitmap->Height()) {
esyslog(tr("softhdev: dirty area too big\n")); esyslog(tr("[softhddev]: dirty area too big\n"));
abort(); abort();
} }
#endif #endif
@@ -334,6 +354,9 @@ void cSoftOsd::Flush(void)
// OSD provider // OSD provider
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/**
** Soft device plugin OSD provider class.
*/
class cSoftOsdProvider:public cOsdProvider class cSoftOsdProvider:public cOsdProvider
{ {
private: private:
@@ -357,12 +380,13 @@ cOsd *cSoftOsdProvider::CreateOsd(int left, int top, uint level)
{ {
//dsyslog("[softhddev]%s: %d, %d, %d\n", __FUNCTION__, left, top, level); //dsyslog("[softhddev]%s: %d, %d, %d\n", __FUNCTION__, left, top, level);
Osd = new cSoftOsd(left, top, level); return Osd = new cSoftOsd(left, top, level);
return Osd;
} }
/** /**
** Returns true if this OSD provider is able to handle a true color OSD. ** Check if this OSD provider is able to handle a true color OSD.
**
** @returns true we are able to handle a true color OSD.
*/ */
bool cSoftOsdProvider::ProvidesTrueColor(void) bool cSoftOsdProvider::ProvidesTrueColor(void)
{ {
@@ -382,11 +406,19 @@ cSoftOsdProvider::cSoftOsdProvider(void)
// cMenuSetupPage // cMenuSetupPage
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/**
** Soft device plugin menu setup page class.
*/
class cMenuSetupSoft:public cMenuSetupPage class cMenuSetupSoft:public cMenuSetupPage
{ {
protected: protected:
///
/// local copies of global setup variables:
/// @{
int MakePrimary; int MakePrimary;
int HideMainMenuEntry; int HideMainMenuEntry;
uint32_t Background;
uint32_t BackgroundAlpha;
int SkipLines; int SkipLines;
int StudioLevels; int StudioLevels;
int Scaling[RESOLUTIONS]; int Scaling[RESOLUTIONS];
@@ -397,11 +429,13 @@ class cMenuSetupSoft:public cMenuSetupPage
int Sharpen[RESOLUTIONS]; int Sharpen[RESOLUTIONS];
int AudioDelay; int AudioDelay;
int AudioPassthrough; int AudioPassthrough;
int AudioDownmix;
int AutoCropInterval; int AutoCropInterval;
int AutoCropDelay; int AutoCropDelay;
int AutoCropTolerance; int AutoCropTolerance;
int SuspendClose; int SuspendClose;
int SuspendX11; int SuspendX11;
/// @}
protected: protected:
virtual void Store(void); virtual void Store(void);
public: public:
@@ -456,6 +490,13 @@ cMenuSetupSoft::cMenuSetupSoft(void)
// //
Add(SeparatorItem(tr("Video"))); Add(SeparatorItem(tr("Video")));
// no unsigned int menu item supported, split background color/alpha
Background = ConfigVideoBackground >> 8;
BackgroundAlpha = ConfigVideoBackground & 0xFF;
Add(new cMenuEditIntItem(tr("video background color (RGB)"),
(int *)&Background, 0, 0x00FFFFFF));
Add(new cMenuEditIntItem(tr("video background color (Alpha)"),
(int *)&BackgroundAlpha, 0, 0xFF));
SkipLines = ConfigVideoSkipLines; SkipLines = ConfigVideoSkipLines;
Add(new cMenuEditIntItem(tr("Skip lines top+bot (pixel)"), &SkipLines, 0, Add(new cMenuEditIntItem(tr("Skip lines top+bot (pixel)"), &SkipLines, 0,
64)); 64));
@@ -478,10 +519,10 @@ cMenuSetupSoft::cMenuSetupSoft(void)
&InverseTelecine[i], trVDR("no"), trVDR("yes"))); &InverseTelecine[i], trVDR("no"), trVDR("yes")));
Denoise[i] = ConfigVideoDenoise[i]; Denoise[i] = ConfigVideoDenoise[i];
Add(new cMenuEditIntItem(tr("Denoise (0..1000) (vdpau)"), &Denoise[i], Add(new cMenuEditIntItem(tr("Denoise (0..1000) (vdpau)"), &Denoise[i],
0, 1000)); 0, 1000, tr("off"), tr("max")));
Sharpen[i] = ConfigVideoSharpen[i]; Sharpen[i] = ConfigVideoSharpen[i];
Add(new cMenuEditIntItem(tr("Sharpen (-1000..1000) (vdpau)"), Add(new cMenuEditIntItem(tr("Sharpen (-1000..1000) (vdpau)"),
&Sharpen[i], -1000, 1000)); &Sharpen[i], -1000, 1000, tr("blur max"), tr("sharpen max")));
} }
// //
// audio // audio
@@ -493,13 +534,16 @@ cMenuSetupSoft::cMenuSetupSoft(void)
AudioPassthrough = ConfigAudioPassthrough; AudioPassthrough = ConfigAudioPassthrough;
Add(new cMenuEditStraItem(tr("Audio pass-through"), &AudioPassthrough, 2, Add(new cMenuEditStraItem(tr("Audio pass-through"), &AudioPassthrough, 2,
passthrough)); passthrough));
AudioDownmix = ConfigAudioDownmix;
Add(new cMenuEditBoolItem(tr("Enable AC-3 downmix"), &AudioDownmix,
trVDR("no"), trVDR("yes")));
// //
// auto-crop // auto-crop
// //
Add(SeparatorItem(tr("Auto-crop"))); Add(SeparatorItem(tr("Auto-crop")));
AutoCropInterval = ConfigAutoCropInterval; AutoCropInterval = ConfigAutoCropInterval;
Add(new cMenuEditIntItem(tr("autocrop interval (frames)"), Add(new cMenuEditIntItem(tr("autocrop interval (frames)"),
&AutoCropInterval, 0, 200)); &AutoCropInterval, 0, 200, tr("off")));
AutoCropDelay = ConfigAutoCropDelay; AutoCropDelay = ConfigAutoCropDelay;
Add(new cMenuEditIntItem(tr("autocrop delay (n * interval)"), Add(new cMenuEditIntItem(tr("autocrop delay (n * interval)"),
&AutoCropDelay, 0, 200)); &AutoCropDelay, 0, 200));
@@ -529,6 +573,9 @@ void cMenuSetupSoft::Store(void)
SetupStore("HideMainMenuEntry", ConfigHideMainMenuEntry = SetupStore("HideMainMenuEntry", ConfigHideMainMenuEntry =
HideMainMenuEntry); HideMainMenuEntry);
ConfigVideoBackground = Background << 8 | (BackgroundAlpha & 0xFF);
SetupStore("Background", ConfigVideoBackground);
VideoSetBackground(ConfigVideoBackground);
SetupStore("SkipLines", ConfigVideoSkipLines = SkipLines); SetupStore("SkipLines", ConfigVideoSkipLines = SkipLines);
VideoSetSkipLines(ConfigVideoSkipLines); VideoSetSkipLines(ConfigVideoSkipLines);
SetupStore("StudioLevels", ConfigVideoStudioLevels = StudioLevels); SetupStore("StudioLevels", ConfigVideoStudioLevels = StudioLevels);
@@ -563,6 +610,8 @@ void cMenuSetupSoft::Store(void)
VideoSetAudioDelay(ConfigVideoAudioDelay); VideoSetAudioDelay(ConfigVideoAudioDelay);
SetupStore("AudioPassthrough", ConfigAudioPassthrough = AudioPassthrough); SetupStore("AudioPassthrough", ConfigAudioPassthrough = AudioPassthrough);
CodecSetAudioPassthrough(ConfigAudioPassthrough); CodecSetAudioPassthrough(ConfigAudioPassthrough);
SetupStore("AudioDownmix", ConfigAudioDownmix = AudioDownmix);
CodecSetAudioDownmix(ConfigAudioDownmix);
SetupStore("AutoCrop.Interval", ConfigAutoCropInterval = AutoCropInterval); SetupStore("AutoCrop.Interval", ConfigAutoCropInterval = AutoCropInterval);
SetupStore("AutoCrop.Delay", ConfigAutoCropDelay = AutoCropDelay); SetupStore("AutoCrop.Delay", ConfigAutoCropDelay = AutoCropDelay);
@@ -629,23 +678,31 @@ cSoftHdPlayer *cSoftHdControl::Player;
*/ */
eOSState cSoftHdControl::ProcessKey(eKeys key) eOSState cSoftHdControl::ProcessKey(eKeys key)
{ {
if (!ISMODELESSKEY(key) || key == kBack || key == kStop) { if (SuspendMode == SUSPEND_NORMAL && (!ISMODELESSKEY(key)
|| key == kMenu || key == kBack || key == kStop)) {
if (Player) { if (Player) {
delete Player; delete Player;
Player = NULL; Player = NULL;
} }
Resume(); Resume();
SuspendMode = 0;
return osEnd; return osEnd;
} }
return osContinue; return osContinue;
} }
/**
** Player control constructor.
*/
cSoftHdControl::cSoftHdControl(void) cSoftHdControl::cSoftHdControl(void)
: cControl(Player = new cSoftHdPlayer) : cControl(Player = new cSoftHdPlayer)
{ {
} }
/**
** Player control destructor.
*/
cSoftHdControl::~cSoftHdControl() cSoftHdControl::~cSoftHdControl()
{ {
if (Player) { if (Player) {
@@ -653,7 +710,140 @@ cSoftHdControl::~cSoftHdControl()
Player = NULL; Player = NULL;
} }
Resume();
dsyslog("[softhddev]%s: resume\n", __FUNCTION__);
//Resume();
}
//////////////////////////////////////////////////////////////////////////////
// cOsdMenu
//////////////////////////////////////////////////////////////////////////////
/**
** Soft device plugin menu class.
*/
class cSoftHdMenu:public cOsdMenu
{
int HotkeyState; ///< current hot-key state
int HotkeyCode; ///< current hot-key code
public:
cSoftHdMenu(const char *, int = 0, int = 0, int = 0, int = 0, int = 0);
virtual ~ cSoftHdMenu();
virtual eOSState ProcessKey(eKeys);
};
/**
** Soft device menu constructor.
*/
cSoftHdMenu::cSoftHdMenu(const char *title, int c0, int c1, int c2, int c3,
int c4)
:cOsdMenu(title, c0, c1, c2, c3, c4)
{
HotkeyState = 0;
SetHasHotkeys();
Add(new cOsdItem(hk(tr("Suspend SoftHdDevice")), osUser1));
}
/**
** Soft device menu destructor.
*/
cSoftHdMenu::~cSoftHdMenu()
{
}
/**
** Handle hot key commands.
**
** @param code numeric hot key code
*/
static void HandleHotkey(int code)
{
switch (code) {
case 10: // disable pass-through
CodecSetAudioPassthrough(ConfigAudioPassthrough = 0);
break;
case 11: // enable pass-through
CodecSetAudioPassthrough(ConfigAudioPassthrough = 1);
break;
case 12: // toggle pass-through
CodecSetAudioPassthrough(ConfigAudioPassthrough ^= 1);
break;
default:
esyslog(tr("[softhddev]: hot key %d is not supported\n"), code);
break;
}
}
/**
** Handle key event.
**
** @param key key event
*/
eOSState cSoftHdMenu::ProcessKey(eKeys key)
{
eOSState state;
//dsyslog("[softhddev]%s: %x\n", __FUNCTION__, key);
switch (HotkeyState) {
case 0: // initial state, waiting for hot key
if (key == kBlue) {
HotkeyState = 1;
return osContinue;
}
break;
case 1:
if (k0 <= key && key <= k9) {
HotkeyCode = key - k0;
HotkeyState = 2;
return osContinue;
}
HotkeyState = 0;
break;
case 2:
if (k0 <= key && key <= k9) {
HotkeyCode *= 10;
HotkeyCode += key - k0;
HotkeyState = 0;
dsyslog("[softhddev]%s: hot-key %d\n", __FUNCTION__,
HotkeyCode);
HandleHotkey(HotkeyCode);
return osEnd;
}
if (key == kOk) {
HotkeyState = 0;
dsyslog("[softhddev]%s: hot-key %d\n", __FUNCTION__,
HotkeyCode);
HandleHotkey(HotkeyCode);
return osEnd;
}
HotkeyState = 0;
break;
}
// call standard function
state = cOsdMenu::ProcessKey(key);
switch (state) {
case osUser1:
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 osEnd;
default:
break;
}
return state;
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@@ -664,7 +854,7 @@ class cSoftHdDevice:public cDevice
{ {
public: public:
cSoftHdDevice(void); cSoftHdDevice(void);
virtual ~ cSoftHdDevice(void); virtual ~ cSoftHdDevice(void);
virtual bool HasDecoder(void) const; virtual bool HasDecoder(void) const;
virtual bool CanReplay(void) const; virtual bool CanReplay(void) const;
@@ -679,12 +869,15 @@ class cSoftHdDevice:public cDevice
virtual bool Flush(int = 0); virtual bool Flush(int = 0);
virtual int64_t GetSTC(void); virtual int64_t GetSTC(void);
virtual void SetVideoDisplayFormat(eVideoDisplayFormat); virtual void SetVideoDisplayFormat(eVideoDisplayFormat);
virtual void SetVideoFormat(bool);
virtual void GetVideoSize(int &, int &, double &); virtual void GetVideoSize(int &, int &, double &);
virtual void GetOsdSize(int &, int &, double &); virtual void GetOsdSize(int &, int &, double &);
virtual int PlayVideo(const uchar *, int); virtual int PlayVideo(const uchar *, int);
virtual int PlayAudio(const uchar *, int, uchar);
//virtual int PlayTsVideo(const uchar *, int); #ifdef USE_TS_VIDEO
#ifndef USE_AUDIO_THREAD // FIXME: testing none threaded virtual int PlayTsVideo(const uchar *, int);
#endif
#if !defined(USE_AUDIO_THREAD) || !defined(NO_TS_AUDIO)
virtual int PlayTsAudio(const uchar *, int); virtual int PlayTsAudio(const uchar *, int);
#endif #endif
virtual void SetAudioChannelDevice(int); virtual void SetAudioChannelDevice(int);
@@ -692,7 +885,6 @@ class cSoftHdDevice:public cDevice
virtual void SetDigitalAudioDevice(bool); virtual void SetDigitalAudioDevice(bool);
virtual void SetAudioTrackDevice(eTrackType); virtual void SetAudioTrackDevice(eTrackType);
virtual void SetVolumeDevice(int); virtual void SetVolumeDevice(int);
virtual int PlayAudio(const uchar *, int, uchar);
// Image Grab facilities // Image Grab facilities
@@ -701,13 +893,13 @@ class cSoftHdDevice:public cDevice
#if 0 #if 0
// SPU facilities // SPU facilities
private: private:
cDvbSpuDecoder * spuDecoder; cDvbSpuDecoder * spuDecoder;
public: public:
virtual cSpuDecoder * GetSpuDecoder(void); virtual cSpuDecoder * GetSpuDecoder(void);
#endif #endif
protected: protected:
virtual void MakePrimaryDevice(bool); virtual void MakePrimaryDevice(bool);
}; };
cSoftHdDevice::cSoftHdDevice(void) cSoftHdDevice::cSoftHdDevice(void)
@@ -717,7 +909,6 @@ cSoftHdDevice::cSoftHdDevice(void)
#if 0 #if 0
spuDecoder = NULL; spuDecoder = NULL;
#endif #endif
SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat));
} }
cSoftHdDevice::~cSoftHdDevice(void) cSoftHdDevice::~cSoftHdDevice(void)
@@ -737,6 +928,13 @@ void cSoftHdDevice::MakePrimaryDevice(bool on)
cDevice::MakePrimaryDevice(on); cDevice::MakePrimaryDevice(on);
if (on) { if (on) {
new cSoftOsdProvider(); new cSoftOsdProvider();
if (SuspendMode == SUSPEND_DETACHED) {
Resume();
SuspendMode = 0;
}
} else if (!SuspendMode) {
Suspend(1, 1, 0);
SuspendMode = SUSPEND_DETACHED;
} }
} }
@@ -768,7 +966,9 @@ bool cSoftHdDevice::CanReplay(void) const
} }
/** /**
** Sets the device into the given play mode. ** Sets the device into the given play mode.
**
** @param play_mode new play mode (Audio/Video/External...)
*/ */
bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode) bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode)
{ {
@@ -787,13 +987,22 @@ bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode)
case pmExtern_THIS_SHOULD_BE_AVOIDED: case pmExtern_THIS_SHOULD_BE_AVOIDED:
dsyslog("[softhddev] play mode external\n"); dsyslog("[softhddev] play mode external\n");
Suspend(1, 1, 0); Suspend(1, 1, 0);
SuspendMode = SUSPEND_EXTERNAL;
return true; return true;
default: default:
dsyslog("[softhddev]playmode not implemented... %d\n", play_mode); dsyslog("[softhddev] playmode not implemented... %d\n", play_mode);
break; break;
} }
::SetPlayMode();
return true; if (SuspendMode) {
if (SuspendMode != SUSPEND_EXTERNAL) {
return true;
}
Resume();
SuspendMode = 0;
}
return::SetPlayMode(play_mode);
} }
/** /**
@@ -810,13 +1019,21 @@ int64_t cSoftHdDevice::GetSTC(void)
/** /**
** Set trick play speed. ** Set trick play speed.
** **
** Every single frame shall then be displayed the given number of
** times.
**
** @param speed trick speed ** @param speed trick speed
*/ */
void cSoftHdDevice::TrickSpeed(int speed) void cSoftHdDevice::TrickSpeed(int speed)
{ {
dsyslog("[softhddev]%s: %d\n", __FUNCTION__, speed); dsyslog("[softhddev]%s: %d\n", __FUNCTION__, speed);
::TrickSpeed(speed);
} }
/**
** Clears all video and audio data from the device.
*/
void cSoftHdDevice::Clear(void) void cSoftHdDevice::Clear(void)
{ {
dsyslog("[softhddev]%s:\n", __FUNCTION__); dsyslog("[softhddev]%s:\n", __FUNCTION__);
@@ -825,6 +1042,9 @@ void cSoftHdDevice::Clear(void)
::Clear(); ::Clear();
} }
/**
** Sets the device into play mode (after a previous trick mode)
*/
void cSoftHdDevice::Play(void) void cSoftHdDevice::Play(void)
{ {
dsyslog("[softhddev]%s:\n", __FUNCTION__); dsyslog("[softhddev]%s:\n", __FUNCTION__);
@@ -902,11 +1122,9 @@ bool cSoftHdDevice::Flush(int timeout_ms)
/** /**
** Sets the video display format to the given one (only useful if this ** Sets the video display format to the given one (only useful if this
** device has an MPEG decoder). ** device has an MPEG decoder).
**
** @note this function isn't called on the initial channel
*/ */
void cSoftHdDevice::SetVideoDisplayFormat( void cSoftHdDevice:: SetVideoDisplayFormat(eVideoDisplayFormat
eVideoDisplayFormat video_display_format) video_display_format)
{ {
static int last = -1; static int last = -1;
@@ -923,6 +1141,23 @@ void cSoftHdDevice::SetVideoDisplayFormat(
} }
} }
/**
** Sets the output video format to either 16:9 or 4:3 (only useful
** if this device has an MPEG decoder).
**
** Should call SetVideoDisplayFormat.
**
** @param video_format16_9 flag true 16:9.
*/
void cSoftHdDevice::SetVideoFormat(bool video_format16_9)
{
dsyslog("[softhddev]%s: %d\n", __FUNCTION__, video_format16_9);
// FIXME: 4:3 / 16:9 video format not supported.
SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat));
}
/** /**
** Returns the width, height and video_aspect ratio of the currently ** Returns the width, height and video_aspect ratio of the currently
** displayed video material. ** displayed video material.
@@ -948,6 +1183,10 @@ void cSoftHdDevice::GetOsdSize(int &width, int &height, double &pixel_aspect)
/** /**
** Play a audio packet. ** Play a audio packet.
**
** @param data exactly one complete PES packet (which is incomplete)
** @param length length of PES packet
** @param id type of audio data this packet holds
*/ */
int cSoftHdDevice::PlayAudio(const uchar * data, int length, uchar id) int cSoftHdDevice::PlayAudio(const uchar * data, int length, uchar id)
{ {
@@ -995,6 +1234,9 @@ void cSoftHdDevice::SetVolumeDevice(int volume)
/** /**
** Play a video packet. ** Play a video packet.
**
** @param data exactly one complete PES packet (which is incomplete)
** @param length length of PES packet
*/ */
int cSoftHdDevice::PlayVideo(const uchar * data, int length) int cSoftHdDevice::PlayVideo(const uchar * data, int length)
{ {
@@ -1003,31 +1245,37 @@ int cSoftHdDevice::PlayVideo(const uchar * data, int length)
return::PlayVideo(data, length); return::PlayVideo(data, length);
} }
#if 0 #ifdef USE_TS_VIDEO
/// ///
/// Play a TS video packet. /// Play a TS video packet.
/// ///
/// @param data ts data buffer
/// @param length ts packet length (188)
///
int cSoftHdDevice::PlayTsVideo(const uchar * data, int length) int cSoftHdDevice::PlayTsVideo(const uchar * data, int length)
{ {
// many code to repeat
} }
#endif #endif
#ifndef USE_AUDIO_THREAD // FIXME: testing none threaded #if !defined(USE_AUDIO_THREAD) || !defined(NO_TS_AUDIO)
/// ///
/// Play a TS audio packet. /// Play a TS audio packet.
/// ///
/// misuse this function as audio poller
///
/// @param data ts data buffer /// @param data ts data buffer
/// @param length ts packet length /// @param length ts packet length (188)
/// ///
int cSoftHdDevice::PlayTsAudio(const uchar * data, int length) int cSoftHdDevice::PlayTsAudio(const uchar * data, int length)
{ {
#ifndef NO_TS_AUDIO
return::PlayTsAudio(data, length);
#else
AudioPoller(); AudioPoller();
return cDevice::PlayTsAudio(data, length); return cDevice::PlayTsAudio(data, length);
#endif
} }
#endif #endif
@@ -1103,6 +1351,11 @@ cPluginSoftHdDevice::~cPluginSoftHdDevice(void)
::SoftHdDeviceExit(); ::SoftHdDeviceExit();
} }
/**
** Return plugin version number.
**
** @returns version number as constant string.
*/
const char *cPluginSoftHdDevice::Version(void) const char *cPluginSoftHdDevice::Version(void)
{ {
return VERSION; return VERSION;
@@ -1161,7 +1414,11 @@ bool cPluginSoftHdDevice::Start(void)
} }
} }
::Start(); if (!::Start()) {
cControl::Launch(new cSoftHdControl);
cControl::Attach();
SuspendMode = SUSPEND_NORMAL;
}
return true; return true;
} }
@@ -1204,11 +1461,13 @@ cOsdObject *cPluginSoftHdDevice::MainMenuAction(void)
{ {
//dsyslog("[softhddev]%s:\n", __FUNCTION__); //dsyslog("[softhddev]%s:\n", __FUNCTION__);
#if 0
//MyDevice->StopReplay(); //MyDevice->StopReplay();
if (!cSoftHdControl::Player) { // not already suspended if (!cSoftHdControl::Player) { // not already suspended
cControl::Launch(new cSoftHdControl); cControl::Launch(new cSoftHdControl);
cControl::Attach(); cControl::Attach();
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11); Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
SuspendMode = SUSPEND_NORMAL;
if (ShutdownHandler.GetUserInactiveTime()) { if (ShutdownHandler.GetUserInactiveTime()) {
dsyslog("[softhddev]%s: set user inactive\n", __FUNCTION__); dsyslog("[softhddev]%s: set user inactive\n", __FUNCTION__);
ShutdownHandler.SetUserInactive(); ShutdownHandler.SetUserInactive();
@@ -1216,6 +1475,8 @@ cOsdObject *cPluginSoftHdDevice::MainMenuAction(void)
} }
return NULL; return NULL;
#endif
return new cSoftHdMenu("SoftHdDevice");
} }
/** /**
@@ -1235,6 +1496,7 @@ void cPluginSoftHdDevice::MainThreadHook(void)
if (ShutdownHandler.IsUserInactive()) { if (ShutdownHandler.IsUserInactive()) {
// this is regular called, but guarded against double calls // this is regular called, but guarded against double calls
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11); Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
SuspendMode = SUSPEND_NORMAL;
} }
::MainThreadHook(); ::MainThreadHook();
@@ -1264,94 +1526,106 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
//dsyslog("[softhddev]%s: '%s' = '%s'\n", __FUNCTION__, name, value); //dsyslog("[softhddev]%s: '%s' = '%s'\n", __FUNCTION__, name, value);
if (!strcmp(name, "MakePrimary")) { if (!strcasecmp(name, "MakePrimary")) {
ConfigMakePrimary = atoi(value); ConfigMakePrimary = atoi(value);
return true; return true;
} }
if (!strcmp(name, "HideMainMenuEntry")) { if (!strcasecmp(name, "HideMainMenuEntry")) {
ConfigHideMainMenuEntry = atoi(value); ConfigHideMainMenuEntry = atoi(value);
return true; return true;
} }
if (!strcmp(name, "SkipLines")) { if (!strcasecmp(name, "Background")) {
VideoSetBackground(ConfigVideoBackground = strtoul(value, NULL, 0));
return true;
}
if (!strcasecmp(name, "SkipLines")) {
VideoSetSkipLines(ConfigVideoSkipLines = atoi(value)); VideoSetSkipLines(ConfigVideoSkipLines = atoi(value));
return true; return true;
} }
if (!strcmp(name, "StudioLevels")) { if (!strcasecmp(name, "StudioLevels")) {
VideoSetStudioLevels(ConfigVideoStudioLevels = atoi(value)); VideoSetStudioLevels(ConfigVideoStudioLevels = atoi(value));
return true; return true;
} }
if (!strcasecmp(name, "60HzMode")) {
VideoSet60HzMode(ConfigVideo60HzMode = atoi(value));
return true;
}
for (i = 0; i < RESOLUTIONS; ++i) { for (i = 0; i < RESOLUTIONS; ++i) {
char buf[128]; char buf[128];
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Scaling"); snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Scaling");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoScaling[i] = atoi(value); ConfigVideoScaling[i] = atoi(value);
VideoSetScaling(ConfigVideoScaling); VideoSetScaling(ConfigVideoScaling);
return true; return true;
} }
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Deinterlace"); snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Deinterlace");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoDeinterlace[i] = atoi(value); ConfigVideoDeinterlace[i] = atoi(value);
VideoSetDeinterlace(ConfigVideoDeinterlace); VideoSetDeinterlace(ConfigVideoDeinterlace);
return true; return true;
} }
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], snprintf(buf, sizeof(buf), "%s.%s", Resolution[i],
"SkipChromaDeinterlace"); "SkipChromaDeinterlace");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoSkipChromaDeinterlace[i] = atoi(value); ConfigVideoSkipChromaDeinterlace[i] = atoi(value);
VideoSetSkipChromaDeinterlace(ConfigVideoSkipChromaDeinterlace); VideoSetSkipChromaDeinterlace(ConfigVideoSkipChromaDeinterlace);
return true; return true;
} }
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "InverseTelecine"); snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "InverseTelecine");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoInverseTelecine[i] = atoi(value); ConfigVideoInverseTelecine[i] = atoi(value);
VideoSetInverseTelecine(ConfigVideoInverseTelecine); VideoSetInverseTelecine(ConfigVideoInverseTelecine);
return true; return true;
} }
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Denoise"); snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Denoise");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoDenoise[i] = atoi(value); ConfigVideoDenoise[i] = atoi(value);
VideoSetDenoise(ConfigVideoDenoise); VideoSetDenoise(ConfigVideoDenoise);
return true; return true;
} }
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Sharpen"); snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Sharpen");
if (!strcmp(name, buf)) { if (!strcasecmp(name, buf)) {
ConfigVideoSharpen[i] = atoi(value); ConfigVideoSharpen[i] = atoi(value);
VideoSetSharpen(ConfigVideoSharpen); VideoSetSharpen(ConfigVideoSharpen);
return true; return true;
} }
} }
if (!strcmp(name, "AudioDelay")) { if (!strcasecmp(name, "AudioDelay")) {
VideoSetAudioDelay(ConfigVideoAudioDelay = atoi(value)); VideoSetAudioDelay(ConfigVideoAudioDelay = atoi(value));
return true; return true;
} }
if (!strcmp(name, "AudioPassthrough")) { if (!strcasecmp(name, "AudioPassthrough")) {
CodecSetAudioPassthrough(ConfigAudioPassthrough = atoi(value)); CodecSetAudioPassthrough(ConfigAudioPassthrough = atoi(value));
return true; return true;
} }
if (!strcasecmp(name, "AudioDownmix")) {
CodecSetAudioDownmix(ConfigAudioDownmix = atoi(value));
return true;
}
if (!strcmp(name, "AutoCrop.Interval")) { if (!strcasecmp(name, "AutoCrop.Interval")) {
VideoSetAutoCrop(ConfigAutoCropInterval = VideoSetAutoCrop(ConfigAutoCropInterval =
atoi(value), ConfigAutoCropDelay, ConfigAutoCropTolerance); atoi(value), ConfigAutoCropDelay, ConfigAutoCropTolerance);
return true; return true;
} }
if (!strcmp(name, "AutoCrop.Delay")) { if (!strcasecmp(name, "AutoCrop.Delay")) {
VideoSetAutoCrop(ConfigAutoCropInterval, ConfigAutoCropDelay = VideoSetAutoCrop(ConfigAutoCropInterval, ConfigAutoCropDelay =
atoi(value), ConfigAutoCropTolerance); atoi(value), ConfigAutoCropTolerance);
return true; return true;
} }
if (!strcmp(name, "AutoCrop.Tolerance")) { if (!strcasecmp(name, "AutoCrop.Tolerance")) {
VideoSetAutoCrop(ConfigAutoCropInterval, ConfigAutoCropDelay, VideoSetAutoCrop(ConfigAutoCropInterval, ConfigAutoCropDelay,
ConfigAutoCropTolerance = atoi(value)); ConfigAutoCropTolerance = atoi(value));
return true; return true;
} }
if (!strcmp(name, "Suspend.Close")) { if (!strcasecmp(name, "Suspend.Close")) {
ConfigSuspendClose = atoi(value); ConfigSuspendClose = atoi(value);
return true; return true;
} }
if (!strcmp(name, "Suspend.X11")) { if (!strcasecmp(name, "Suspend.X11")) {
ConfigSuspendX11 = atoi(value); ConfigSuspendX11 = atoi(value);
return true; return true;
} }
@@ -1385,6 +1659,9 @@ const char **cPluginSoftHdDevice::SVDRPHelpPages(void)
static const char *text[] = { static const char *text[] = {
"SUSP\n" " Suspend plugin.\n", "SUSP\n" " Suspend plugin.\n",
"RESU\n" " Resume plugin.\n", "RESU\n" " Resume plugin.\n",
"DETA\n" " Detach plugin.\n",
"ATTA\n" " Attach plugin.\n",
"HOTK key\n" " Execute hotkey.\n",
NULL NULL
}; };
@@ -1393,6 +1670,10 @@ const char **cPluginSoftHdDevice::SVDRPHelpPages(void)
/** /**
** Handle SVDRP commands. ** Handle SVDRP commands.
**
** @param command SVDRP command
** @param option all command arguments
** @param reply_code reply code
*/ */
cString cPluginSoftHdDevice::SVDRPCommand(const char *command, cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
__attribute__ ((unused)) const char *option, __attribute__ ((unused)) const char *option,
@@ -1403,12 +1684,16 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
return "SoftHdDevice already suspended"; return "SoftHdDevice already suspended";
} }
// should be after suspend, but SetPlayMode resumes // should be after suspend, but SetPlayMode resumes
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
SuspendMode = SUSPEND_NORMAL;
cControl::Launch(new cSoftHdControl); cControl::Launch(new cSoftHdControl);
cControl::Attach(); cControl::Attach();
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
return "SoftHdDevice is suspended"; return "SoftHdDevice is suspended";
} }
if (!strcasecmp(command, "RESU")) { if (!strcasecmp(command, "RESU")) {
if (SuspendMode != SUSPEND_NORMAL) {
return "can't resume SoftHdDevice";
}
if (ShutdownHandler.GetUserInactiveTime()) { if (ShutdownHandler.GetUserInactiveTime()) {
ShutdownHandler.SetUserInactiveTimeout(); ShutdownHandler.SetUserInactiveTimeout();
} }
@@ -1416,8 +1701,43 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
cControl::Shutdown(); // not need, if not suspended cControl::Shutdown(); // not need, if not suspended
} }
Resume(); Resume();
SuspendMode = 0;
return "SoftHdDevice is resumed"; return "SoftHdDevice is resumed";
} }
if (!strcasecmp(command, "DETA")) {
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();
return "SoftHdDevice is detached";
}
if (!strcasecmp(command, "ATTA")) {
if (SuspendMode != SUSPEND_DETACHED) {
return "can't attach SoftHdDevice not detached";
}
if (ShutdownHandler.GetUserInactiveTime()) {
ShutdownHandler.SetUserInactiveTimeout();
}
if (cSoftHdControl::Player) { // suspended
cControl::Shutdown(); // not need, if not suspended
}
Resume();
SuspendMode = 0;
return "SoftHdDevice is attached";
}
if (!strcasecmp(command, "HOTK")) {
int hotk;
hotk = strtol(option, NULL, 0);
HandleHotkey(hotk);
return "hot-key executed";
}
return NULL; return NULL;
} }

280
video.c
View File

@@ -41,10 +41,13 @@
#define USE_GRAB ///< experimental grab code #define USE_GRAB ///< experimental grab code
#define noUSE_GLX ///< outdated GLX code #define noUSE_GLX ///< outdated GLX code
#define noUSE_DOUBLEBUFFER ///< use GLX double buffers #define noUSE_DOUBLEBUFFER ///< use GLX double buffers
//#define USE_VAAPI ///< enable vaapi support //#define USE_VAAPI ///< enable vaapi support
//#define USE_VDPAU ///< enable vdpau support //#define USE_VDPAU ///< enable vdpau support
#define noUSE_BITMAP ///< use vdpau bitmap surface #define noUSE_BITMAP ///< use vdpau bitmap surface
//#define AV_INFO ///< log a/v sync informations
#ifndef AV_INFO_TIME
#define AV_INFO_TIME (50 * 60) ///< a/v info every minute
#endif
#define USE_VIDEO_THREAD ///< run decoder in an own thread #define USE_VIDEO_THREAD ///< run decoder in an own thread
@@ -70,6 +73,7 @@
#endif #endif
#include <pthread.h> #include <pthread.h>
#include <time.h> #include <time.h>
#include <signal.h>
#ifndef HAVE_PTHREAD_NAME #ifndef HAVE_PTHREAD_NAME
/// only available with newer glibc /// only available with newer glibc
#define pthread_setname_np(thread, name) #define pthread_setname_np(thread, name)
@@ -127,6 +131,11 @@ typedef enum
#ifdef USE_GLX #ifdef USE_GLX
#include <va/va_glx.h> #include <va/va_glx.h>
#endif #endif
#ifndef VA_SURFACE_ATTRIB_SETTABLE
/// make source compatible with old libva
#define vaCreateSurfaces(d, f, w, h, s, ns, a, na) \
vaCreateSurfaces(d, w, h, f, ns, s)
#endif
#endif #endif
#ifdef USE_VDPAU #ifdef USE_VDPAU
@@ -225,6 +234,7 @@ typedef struct _video_module_
void (*const RenderFrame) (VideoHwDecoder *, const AVCodecContext *, void (*const RenderFrame) (VideoHwDecoder *, const AVCodecContext *,
const AVFrame *); const AVFrame *);
uint8_t *(*const GrabOutput)(int *, int *, int *); uint8_t *(*const GrabOutput)(int *, int *, int *);
void (*const SetBackground) (uint32_t);
void (*const SetVideoMode) (void); void (*const SetVideoMode) (void);
void (*const ResetAutoCrop) (void); void (*const ResetAutoCrop) (void);
@@ -262,6 +272,8 @@ typedef struct _video_module_
// Variables // Variables
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
char VideoIgnoreRepeatPict; ///< disable repeat pict warning
static Display *XlibDisplay; ///< Xlib X11 display static Display *XlibDisplay; ///< Xlib X11 display
static xcb_connection_t *Connection; ///< xcb connection static xcb_connection_t *Connection; ///< xcb connection
static xcb_colormap_t VideoColormap; ///< video colormap static xcb_colormap_t VideoColormap; ///< video colormap
@@ -282,6 +294,7 @@ static char VideoSurfaceModesChanged; ///< flag surface modes changed
/// flag use transparent OSD. /// flag use transparent OSD.
static const char VideoTransparentOsd = 1; static const char VideoTransparentOsd = 1;
static uint32_t VideoBackground; ///< video background color
static int VideoSkipLines; ///< skip video lines top/bottom static int VideoSkipLines; ///< skip video lines top/bottom
static char VideoStudioLevels; ///< flag use studio levels static char VideoStudioLevels; ///< flag use studio levels
@@ -318,7 +331,7 @@ int VideoAudioDelay;
/// Default zoom mode /// Default zoom mode
static VideoZoomModes Video4to3ZoomMode; static VideoZoomModes Video4to3ZoomMode;
//static char VideoSoftStartSync; ///< soft start sync audio/video static char VideoSoftStartSync = 1; ///< soft start sync audio/video
static char Video60HzMode; ///< handle 60hz displays static char Video60HzMode; ///< handle 60hz displays
@@ -327,7 +340,7 @@ static xcb_atom_t NetWmState; ///< wm-state message atom
static xcb_atom_t NetWmStateFullscreen; ///< fullscreen wm-state message atom static xcb_atom_t NetWmStateFullscreen; ///< fullscreen wm-state message atom
extern uint32_t VideoSwitch; ///< ticks for channel switch extern uint32_t VideoSwitch; ///< ticks for channel switch
extern atomic_t VideoPacketsFilled; ///< how many of the buffer is used extern void AudioVideoReady(void); ///< tell audio video is ready
#ifdef USE_VIDEO_THREAD #ifdef USE_VIDEO_THREAD
@@ -354,6 +367,7 @@ static int64_t VideoDeltaPTS; ///< FIXME: fix pts
static void VideoThreadLock(void); ///< lock video thread static void VideoThreadLock(void); ///< lock video thread
static void VideoThreadUnlock(void); ///< unlock video thread static void VideoThreadUnlock(void); ///< unlock video thread
static void VideoThreadExit(void); ///< exit/kill video thread
#if defined(DEBUG) || defined(AV_INFO) #if defined(DEBUG) || defined(AV_INFO)
/// ///
@@ -368,6 +382,9 @@ static const char *VideoTimeStampString(int64_t ts)
int ss; int ss;
int uu; int uu;
if (ts == (int64_t) AV_NOPTS_VALUE) {
return "--:--:--.---";
}
idx ^= 1; // support two static buffers idx ^= 1; // support two static buffers
ts = ts / 90; ts = ts / 90;
uu = ts % 1000; uu = ts % 1000;
@@ -394,19 +411,20 @@ static void VideoSetPts(int64_t * pts_p, int interlaced, const AVFrame * frame)
int64_t pts; int64_t pts;
// update video clock // update video clock
if ((uint64_t) * pts_p != AV_NOPTS_VALUE) { if (*pts_p != (int64_t) AV_NOPTS_VALUE) {
*pts_p += interlaced ? 40 * 90 : 20 * 90; *pts_p += interlaced ? 40 * 90 : 20 * 90;
} }
//av_opt_ptr(avcodec_get_frame_class(), frame, "best_effort_timestamp");
//pts = frame->best_effort_timestamp; //pts = frame->best_effort_timestamp;
pts = frame->pkt_pts; pts = frame->pkt_pts;
if ((uint64_t) pts == AV_NOPTS_VALUE || !pts) { if (pts == (int64_t) AV_NOPTS_VALUE || !pts) {
// libav: 0.8pre didn't set pts // libav: 0.8pre didn't set pts
pts = frame->pkt_dts; pts = frame->pkt_dts;
} }
// libav: sets only pkt_dts which can be 0 // libav: sets only pkt_dts which can be 0
if (pts && (uint64_t) pts != AV_NOPTS_VALUE) { if (pts && pts != (int64_t) AV_NOPTS_VALUE) {
// build a monotonic pts // build a monotonic pts
if ((uint64_t) * pts_p != AV_NOPTS_VALUE) { if (*pts_p != (int64_t) AV_NOPTS_VALUE) {
int64_t delta; int64_t delta;
delta = pts - *pts_p; delta = pts - *pts_p;
@@ -1329,6 +1347,7 @@ struct _vaapi_decoder_
struct timespec FrameTime; ///< time of last display struct timespec FrameTime; ///< time of last display
int64_t PTS; ///< video PTS clock int64_t PTS; ///< video PTS clock
int StartCounter; ///< number of start frames
int FramesDuped; ///< number of frames duplicated int FramesDuped; ///< number of frames duplicated
int FramesMissed; ///< number of frames missed int FramesMissed; ///< number of frames missed
int FramesDropped; ///< number of frames dropped int FramesDropped; ///< number of frames dropped
@@ -1345,6 +1364,9 @@ static void VaapiBlackSurface(VaapiDecoder *);
/// forward destroy deinterlace images /// forward destroy deinterlace images
static void VaapiDestroyDeinterlaceImages(VaapiDecoder *); static void VaapiDestroyDeinterlaceImages(VaapiDecoder *);
/// forward definition release surface
static void VaapiReleaseSurface(VaapiDecoder *, VASurfaceID);
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// VA-API Functions // VA-API Functions
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -1457,9 +1479,9 @@ static void VaapiCreateSurfaces(VaapiDecoder * decoder, int width, int height)
decoder->SurfaceFreeN = decoder->SurfacesNeeded; decoder->SurfaceFreeN = decoder->SurfacesNeeded;
// VA_RT_FORMAT_YUV420 VA_RT_FORMAT_YUV422 VA_RT_FORMAT_YUV444 // VA_RT_FORMAT_YUV420 VA_RT_FORMAT_YUV422 VA_RT_FORMAT_YUV444
if (vaCreateSurfaces(decoder->VaDisplay, width, height, if (vaCreateSurfaces(decoder->VaDisplay, VA_RT_FORMAT_YUV420, width,
VA_RT_FORMAT_YUV420, decoder->SurfaceFreeN, height, decoder->SurfacesFree, decoder->SurfaceFreeN, NULL,
decoder->SurfacesFree) != VA_STATUS_SUCCESS) { 0) != VA_STATUS_SUCCESS) {
Fatal(_("video/vaapi: can't create %d surfaces\n"), Fatal(_("video/vaapi: can't create %d surfaces\n"),
decoder->SurfaceFreeN); decoder->SurfaceFreeN);
// FIXME: write error handler / fallback // FIXME: write error handler / fallback
@@ -1498,9 +1520,6 @@ static void VaapiDestroySurfaces(VaapiDecoder * decoder)
// FIXME surfaces used for output // FIXME surfaces used for output
} }
/// forward definition release surface
static void VaapiReleaseSurface(VaapiDecoder *, VASurfaceID);
/// ///
/// Get a free surface. /// Get a free surface.
/// ///
@@ -1800,10 +1819,13 @@ static void VaapiCleanup(VaapiDecoder * decoder)
if (decoder->DeintImages[0].image_id != VA_INVALID_ID) { if (decoder->DeintImages[0].image_id != VA_INVALID_ID) {
VaapiDestroyDeinterlaceImages(decoder); VaapiDestroyDeinterlaceImages(decoder);
} }
decoder->SurfaceRead = 0;
decoder->SurfaceWrite = 0;
decoder->SurfaceField = 1; decoder->SurfaceField = 1;
//decoder->FrameCounter = 0; //decoder->FrameCounter = 0;
decoder->StartCounter = 0;
decoder->PTS = AV_NOPTS_VALUE; decoder->PTS = AV_NOPTS_VALUE;
VideoDeltaPTS = 0; VideoDeltaPTS = 0;
} }
@@ -1923,8 +1945,8 @@ static void Vaapi1080i(void)
Error(_("codec: can't create config")); Error(_("codec: can't create config"));
return; return;
} }
if (vaCreateSurfaces(VaDisplay, 1920, 1080, VA_RT_FORMAT_YUV420, 32, if (vaCreateSurfaces(VaDisplay, VA_RT_FORMAT_YUV420, 1920, 1080, surfaces,
surfaces) != VA_STATUS_SUCCESS) { 32, NULL, 0) != VA_STATUS_SUCCESS) {
Error(_("video/vaapi: can't create surfaces\n")); Error(_("video/vaapi: can't create surfaces\n"));
return; return;
} }
@@ -2058,6 +2080,17 @@ static int VaapiInit(const char *display_name)
attr.value ? _("direct mapped") : _("copied")); attr.value ? _("direct mapped") : _("copied"));
// FIXME: handle the cases: new liba: Don't use it. // FIXME: handle the cases: new liba: Don't use it.
attr.type = VADisplayAttribBackgroundColor;
attr.flags = VA_DISPLAY_ATTRIB_SETTABLE;
if (vaGetDisplayAttributes(VaDisplay, &attr, 1) != VA_STATUS_SUCCESS) {
Error(_("video/vaapi: Can't get background-color attribute\n"));
attr.value = 1;
}
Info(_("video/vaapi: background-color is %s\n"),
attr.value ? _("supported") : _("unsupported"));
// FIXME: VaapiSetBackground(VideoBackground);
#if 0 #if 0
// //
// check the chroma format // check the chroma format
@@ -2970,10 +3003,10 @@ static void VaapiQueueSurface(VaapiDecoder * decoder, VASurfaceID surface,
if ((old = decoder->SurfacesRb[decoder->SurfaceWrite]) if ((old = decoder->SurfacesRb[decoder->SurfaceWrite])
!= VA_INVALID_ID) { != VA_INVALID_ID) {
#if 0
if (vaSyncSurface(decoder->VaDisplay, old) != VA_STATUS_SUCCESS) { if (vaSyncSurface(decoder->VaDisplay, old) != VA_STATUS_SUCCESS) {
Error(_("video/vaapi: vaSyncSurface failed\n")); Error(_("video/vaapi: vaSyncSurface failed\n"));
} }
#if 0
VASurfaceStatus status; VASurfaceStatus status;
if (vaQuerySurfaceStatus(decoder->VaDisplay, old, &status) if (vaQuerySurfaceStatus(decoder->VaDisplay, old, &status)
@@ -3053,9 +3086,9 @@ static void VaapiBlackSurface(VaapiDecoder * decoder)
} }
if (decoder->BlackSurface == VA_INVALID_ID) { if (decoder->BlackSurface == VA_INVALID_ID) {
if (vaCreateSurfaces(decoder->VaDisplay, VideoWindowWidth, if (vaCreateSurfaces(decoder->VaDisplay, VA_RT_FORMAT_YUV420,
VideoWindowHeight, VA_RT_FORMAT_YUV420, 1, VideoWindowWidth, VideoWindowHeight, &decoder->BlackSurface, 1,
&decoder->BlackSurface) != VA_STATUS_SUCCESS) { NULL, 0) != VA_STATUS_SUCCESS) {
Error(_("video/vaapi: can't create a surface\n")); Error(_("video/vaapi: can't create a surface\n"));
return; return;
} }
@@ -4222,7 +4255,7 @@ static void VaapiAdvanceFrame(void)
Warning(_ Warning(_
("video: display buffer empty, duping frame (%d/%d) %d\n"), ("video: display buffer empty, duping frame (%d/%d) %d\n"),
decoder->FramesDuped, decoder->FrameCounter, decoder->FramesDuped, decoder->FrameCounter,
atomic_read(&VideoPacketsFilled)); VideoGetBuffers());
} }
last_warned_frame = decoder->FrameCounter; last_warned_frame = decoder->FrameCounter;
if (!(decoder->FramesDisplayed % 300)) { if (!(decoder->FramesDisplayed % 300)) {
@@ -4355,22 +4388,30 @@ static void VaapiSyncDisplayFrame(VaapiDecoder * decoder)
filled = atomic_read(&decoder->SurfacesFilled); filled = atomic_read(&decoder->SurfacesFilled);
// FIXME: audio not known assume 333ms delay // FIXME: audio not known assume 333ms delay
decoder->StartCounter++;
if (!VideoSoftStartSync && decoder->StartCounter < 60
&& (audio_clock == (int64_t) AV_NOPTS_VALUE
|| video_clock > audio_clock + VideoAudioDelay + 120 * 90)) {
Debug(3, "video: initial slow down %d\n", decoder->StartCounter);
decoder->DupNextFrame = 2;
}
if (decoder->DupNextFrame) { if (decoder->DupNextFrame) {
++decoder->FramesDuped;
decoder->DupNextFrame--; decoder->DupNextFrame--;
} else if ((uint64_t) audio_clock != AV_NOPTS_VALUE ++decoder->FramesDuped;
&& (uint64_t) video_clock != AV_NOPTS_VALUE) { } else if (audio_clock != (int64_t) AV_NOPTS_VALUE
&& video_clock != (int64_t) AV_NOPTS_VALUE) {
// both clocks are known // both clocks are known
if (abs(video_clock - audio_clock) > 5000 * 90) { if (abs(video_clock - audio_clock) > 5000 * 90) {
Debug(3, "video: pts difference too big\n"); Debug(3, "video: pts difference too big\n");
} else if (video_clock > audio_clock + VideoAudioDelay + 80 * 90) { } else if (video_clock > audio_clock + VideoAudioDelay + 100 * 90) {
Debug(3, "video: slow down video\n"); Debug(3, "video: slow down video\n");
decoder->DupNextFrame += 2; decoder->DupNextFrame += 2;
} else if (video_clock > audio_clock + VideoAudioDelay + 30 * 90) { } else if (video_clock > audio_clock + VideoAudioDelay + 45 * 90) {
Debug(3, "video: slow down video\n"); Debug(3, "video: slow down video\n");
decoder->DupNextFrame++; decoder->DupNextFrame++;
} else if (audio_clock + VideoAudioDelay > video_clock + 40 * 90 } else if (audio_clock + VideoAudioDelay > video_clock + 15 * 90
&& filled > 1) { && filled > 1) {
Debug(3, "video: speed up video\n"); Debug(3, "video: speed up video\n");
decoder->DropNextFrame = 1; decoder->DropNextFrame = 1;
@@ -4379,11 +4420,12 @@ static void VaapiSyncDisplayFrame(VaapiDecoder * decoder)
#if defined(DEBUG) || defined(AV_INFO) #if defined(DEBUG) || defined(AV_INFO)
// debug audio/video sync // debug audio/video sync
if (decoder->DupNextFrame || decoder->DropNextFrame if (decoder->DupNextFrame || decoder->DropNextFrame
|| !(decoder->FramesDisplayed % (50 * 10))) { || !(decoder->FramesDisplayed % AV_INFO_TIME)) {
Info("video: %s%+5" PRId64 " %4" PRId64 " %3d/\\ms %3d v-buf\n", Info("video: %s%+5" PRId64 " %4" PRId64 " %3d/\\ms %3d v-buf\n",
VideoTimeStampString(video_clock), VideoTimeStampString(video_clock),
(video_clock - audio_clock) / 90, AudioGetDelay() / 90, abs((video_clock - audio_clock) / 90) <
(int)VideoDeltaPTS / 90, atomic_read(&VideoPacketsFilled)); 9999 ? ((video_clock - audio_clock) / 90) : 88888,
AudioGetDelay() / 90, (int)VideoDeltaPTS / 90, VideoGetBuffers());
} }
#endif #endif
} }
@@ -4459,7 +4501,7 @@ static void VaapiSyncRenderFrame(VaapiDecoder * decoder,
static int64_t VaapiGetClock(const VaapiDecoder * decoder) static int64_t VaapiGetClock(const VaapiDecoder * decoder)
{ {
// pts is the timestamp of the latest decoded frame // pts is the timestamp of the latest decoded frame
if (!decoder || (uint64_t) decoder->PTS == AV_NOPTS_VALUE) { if (!decoder || decoder->PTS == (int64_t) AV_NOPTS_VALUE) {
return AV_NOPTS_VALUE; return AV_NOPTS_VALUE;
} }
// subtract buffered decoded frames // subtract buffered decoded frames
@@ -4472,6 +4514,16 @@ static int64_t VaapiGetClock(const VaapiDecoder * decoder)
2); 2);
} }
///
/// Set VA-API background color.
///
/// @param rgba 32 bit RGBA color.
///
static void VaapiSetBackground( __attribute__ ((unused)) uint32_t rgba)
{
Error(_("video/vaapi: FIXME: SetBackground not supported\n"));
}
/// ///
/// Set VA-API video mode. /// Set VA-API video mode.
/// ///
@@ -4769,6 +4821,7 @@ static const VideoModule VaapiModule = {
.RenderFrame = (void (*const) (VideoHwDecoder *, .RenderFrame = (void (*const) (VideoHwDecoder *,
const AVCodecContext *, const AVFrame *))VaapiSyncRenderFrame, const AVCodecContext *, const AVFrame *))VaapiSyncRenderFrame,
.GrabOutput = NULL, .GrabOutput = NULL,
.SetBackground = VaapiSetBackground,
.SetVideoMode = VaapiSetVideoMode, .SetVideoMode = VaapiSetVideoMode,
.ResetAutoCrop = VaapiResetAutoCrop, .ResetAutoCrop = VaapiResetAutoCrop,
.Thread = VaapiDisplayHandlerThread, .Thread = VaapiDisplayHandlerThread,
@@ -4850,6 +4903,7 @@ typedef struct _vdpau_decoder_
struct timespec FrameTime; ///< time of last display struct timespec FrameTime; ///< time of last display
int64_t PTS; ///< video PTS clock int64_t PTS; ///< video PTS clock
int StartCounter; ///< number of start frames
int FramesDuped; ///< number of frames duplicated int FramesDuped; ///< number of frames duplicated
int FramesMissed; ///< number of frames missed int FramesMissed; ///< number of frames missed
int FramesDropped; ///< number of frames dropped int FramesDropped; ///< number of frames dropped
@@ -4868,8 +4922,9 @@ static VdpGetProcAddress *VdpauGetProcAddress; ///< entry point to use
/// presentation queue target /// presentation queue target
static VdpPresentationQueueTarget VdpauQueueTarget; static VdpPresentationQueueTarget VdpauQueueTarget;
static VdpPresentationQueue VdpauQueue; ///< presentation queue static VdpPresentationQueue VdpauQueue; ///< presentation queue
static VdpColor VdpauBackgroundColor[1]; ///< queue background color static VdpColor VdpauQueueBackgroundColor[1]; ///< queue background color
static int VdpauBackground; ///< background supported
static int VdpauHqScalingMax; ///< highest supported scaling level static int VdpauHqScalingMax; ///< highest supported scaling level
static int VdpauTemporal; ///< temporal deinterlacer supported static int VdpauTemporal; ///< temporal deinterlacer supported
static int VdpauTemporalSpatial; ///< temporal spatial deint. supported static int VdpauTemporalSpatial; ///< temporal spatial deint. supported
@@ -5139,9 +5194,10 @@ static void VdpauMixerSetup(VdpauDecoder * decoder)
VdpVideoMixerFeature features[15]; VdpVideoMixerFeature features[15];
VdpBool enables[15]; VdpBool enables[15];
int feature_n; int feature_n;
VdpVideoMixerAttribute attributes[4]; VdpVideoMixerAttribute attributes[5];
void const *attribute_value_ptrs[4]; void const *attribute_value_ptrs[5];
int attribute_n; int attribute_n;
VdpColor background_color[1];
uint8_t skip_chroma_value; uint8_t skip_chroma_value;
float noise_reduction_level; float noise_reduction_level;
float sharpness_level; float sharpness_level;
@@ -5217,7 +5273,20 @@ static void VdpauMixerSetup(VdpauDecoder * decoder)
VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MIN_LUMA VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MIN_LUMA
VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MAX_LUMA VDP_VIDEO_MIXER_ATTRIBUTE_LUMA_KEY_MAX_LUMA
*/ */
attribute_n = 0; attribute_n = 0;
// none video-area background color
if (VdpauBackground) {
background_color->red = (VideoBackground >> 24) / 255.0;
background_color->green = ((VideoBackground >> 16) & 0xFF) / 255.0;
background_color->blue = ((VideoBackground >> 8) & 0xFF) / 255.0;
background_color->alpha = (VideoBackground & 0xFF) / 255.0;
attributes[attribute_n] = VDP_VIDEO_MIXER_ATTRIBUTE_BACKGROUND_COLOR;
attribute_value_ptrs[attribute_n++] = background_color;
Debug(3, "video/vdpau: background color %f/%f/%f/%f\n",
background_color->red, background_color->green,
background_color->blue, background_color->alpha);
}
if (VdpauSkipChroma) { if (VdpauSkipChroma) {
skip_chroma_value = VideoSkipChromaDeinterlace[decoder->Resolution]; skip_chroma_value = VideoSkipChromaDeinterlace[decoder->Resolution];
attributes[attribute_n] attributes[attribute_n]
@@ -5511,6 +5580,8 @@ static void VdpauCleanup(VdpauDecoder * decoder)
decoder->SurfaceField = 0; decoder->SurfaceField = 0;
//decoder->FrameCounter = 0;
decoder->StartCounter = 0;
decoder->PTS = AV_NOPTS_VALUE; decoder->PTS = AV_NOPTS_VALUE;
VideoDeltaPTS = 0; VideoDeltaPTS = 0;
} }
@@ -5588,11 +5659,12 @@ static void VdpauInitOutputQueue(void)
return; return;
} }
VdpauBackgroundColor->red = 0.01; VdpauQueueBackgroundColor->red = 0.01;
VdpauBackgroundColor->green = 0.02; VdpauQueueBackgroundColor->green = 0.02;
VdpauBackgroundColor->blue = 0.03; VdpauQueueBackgroundColor->blue = 0.03;
VdpauBackgroundColor->alpha = 1.00; VdpauQueueBackgroundColor->alpha = 1.00;
VdpauPresentationQueueSetBackgroundColor(VdpauQueue, VdpauBackgroundColor); VdpauPresentationQueueSetBackgroundColor(VdpauQueue,
VdpauQueueBackgroundColor);
// //
// Create display output surfaces // Create display output surfaces
@@ -5888,6 +5960,16 @@ static int VdpauInit(const char *display_name)
// //
// Cache some features // Cache some features
// //
status =
VdpauVideoMixerQueryFeatureSupport(VdpauDevice,
VDP_VIDEO_MIXER_ATTRIBUTE_BACKGROUND_COLOR, &flag);
if (status != VDP_STATUS_OK) {
Error(_("video/vdpau: can't query feature '%s': %s\n"),
"background-color", VdpauGetErrorString(status));
} else {
VdpauBackground = flag;
}
status = status =
VdpauVideoMixerQueryFeatureSupport(VdpauDevice, VdpauVideoMixerQueryFeatureSupport(VdpauDevice,
VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, &flag); VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, &flag);
@@ -5948,8 +6030,6 @@ static int VdpauInit(const char *display_name)
VdpauSkipChroma = flag; VdpauSkipChroma = flag;
} }
// VDP_VIDEO_MIXER_ATTRIBUTE_BACKGROUND_COLOR
if (VdpauHqScalingMax) { if (VdpauHqScalingMax) {
Info(_("video/vdpau: highest supported high quality scaling %d\n"), Info(_("video/vdpau: highest supported high quality scaling %d\n"),
VdpauHqScalingMax - VdpauHqScalingMax -
@@ -7231,7 +7311,7 @@ static void VdpauAdvanceFrame(void)
Warning(_ Warning(_
("video: display buffer empty, duping frame (%d/%d) %d\n"), ("video: display buffer empty, duping frame (%d/%d) %d\n"),
decoder->FramesDuped, decoder->FrameCounter, decoder->FramesDuped, decoder->FrameCounter,
atomic_read(&VideoPacketsFilled)); VideoGetBuffers());
} }
last_warned_frame = decoder->FrameCounter; last_warned_frame = decoder->FrameCounter;
if (!(decoder->FramesDisplayed % 300)) { if (!(decoder->FramesDisplayed % 300)) {
@@ -7365,21 +7445,30 @@ static void VdpauSyncDisplayFrame(VdpauDecoder * decoder)
filled = atomic_read(&decoder->SurfacesFilled); filled = atomic_read(&decoder->SurfacesFilled);
// FIXME: audio not known assume 333ms delay // FIXME: audio not known assume 333ms delay
decoder->StartCounter++;
if (!VideoSoftStartSync && decoder->StartCounter < 60
&& (audio_clock == (int64_t) AV_NOPTS_VALUE
|| video_clock > audio_clock + VideoAudioDelay + 120 * 90)) {
Debug(3, "video: initial slow down %d\n", decoder->StartCounter);
decoder->DupNextFrame = 2;
}
if (decoder->DupNextFrame) { if (decoder->DupNextFrame) {
decoder->DupNextFrame--; decoder->DupNextFrame--;
} else if ((uint64_t) audio_clock != AV_NOPTS_VALUE ++decoder->FramesDuped;
&& (uint64_t) video_clock != AV_NOPTS_VALUE) { } else if (audio_clock != (int64_t) AV_NOPTS_VALUE
&& video_clock != (int64_t) AV_NOPTS_VALUE) {
// both clocks are known // both clocks are known
if (abs(video_clock - audio_clock) > 5000 * 90) { if (abs(video_clock - audio_clock) > 5000 * 90) {
Debug(3, "video: pts difference too big\n"); Debug(3, "video: pts difference too big\n");
} else if (video_clock > audio_clock + VideoAudioDelay + 80 * 90) { } else if (video_clock > audio_clock + VideoAudioDelay + 100 * 90) {
Debug(3, "video: slow down video\n"); Debug(3, "video: slow down video\n");
decoder->DupNextFrame += 2; decoder->DupNextFrame += 2;
} else if (video_clock > audio_clock + VideoAudioDelay + 30 * 90) { } else if (video_clock > audio_clock + VideoAudioDelay + 45 * 90) {
Debug(3, "video: slow down video\n"); Debug(3, "video: slow down video\n");
decoder->DupNextFrame++; decoder->DupNextFrame++;
} else if (audio_clock + VideoAudioDelay > video_clock + 40 * 90 } else if (audio_clock + VideoAudioDelay > video_clock + 15 * 90
&& filled > 1 + 2 * decoder->Interlaced) { && filled > 1 + 2 * decoder->Interlaced) {
Debug(3, "video: speed up video\n"); Debug(3, "video: speed up video\n");
decoder->DropNextFrame = 1; decoder->DropNextFrame = 1;
@@ -7388,11 +7477,12 @@ static void VdpauSyncDisplayFrame(VdpauDecoder * decoder)
#if defined(DEBUG) || defined(AV_INFO) #if defined(DEBUG) || defined(AV_INFO)
// debug audio/video sync // debug audio/video sync
if (decoder->DupNextFrame || decoder->DropNextFrame if (decoder->DupNextFrame || decoder->DropNextFrame
|| !(decoder->FramesDisplayed % (50 * 10))) { || !(decoder->FramesDisplayed % AV_INFO_TIME)) {
Info("video: %s%+5" PRId64 " %4" PRId64 " %3d/\\ms %3d v-buf\n", Info("video: %s%+5" PRId64 " %4" PRId64 " %3d/\\ms %3d v-buf\n",
VideoTimeStampString(video_clock), VideoTimeStampString(video_clock),
(video_clock - audio_clock) / 90, AudioGetDelay() / 90, abs((video_clock - audio_clock) / 90) <
(int)VideoDeltaPTS / 90, atomic_read(&VideoPacketsFilled)); 9999 ? ((video_clock - audio_clock) / 90) : 88888,
AudioGetDelay() / 90, (int)VideoDeltaPTS / 90, VideoGetBuffers());
} }
#endif #endif
} }
@@ -7409,14 +7499,9 @@ static void VdpauSyncRenderFrame(VdpauDecoder * decoder,
{ {
VideoSetPts(&decoder->PTS, decoder->Interlaced, frame); VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
if (VdpauPreemption) { // display preempted
return;
}
#ifdef DEBUG
if (!atomic_read(&decoder->SurfacesFilled)) { if (!atomic_read(&decoder->SurfacesFilled)) {
Debug(3, "video: new stream frame %d\n", GetMsTicks() - VideoSwitch); Debug(3, "video: new stream frame %d\n", GetMsTicks() - VideoSwitch);
} }
#endif
if (decoder->DropNextFrame) { // drop frame requested if (decoder->DropNextFrame) { // drop frame requested
++decoder->FramesDropped; ++decoder->FramesDropped;
@@ -7428,6 +7513,9 @@ static void VdpauSyncRenderFrame(VdpauDecoder * decoder,
decoder->DropNextFrame--; decoder->DropNextFrame--;
return; return;
} }
if (VdpauPreemption) { // display preempted
return;
}
// if video output buffer is full, wait and display surface. // if video output buffer is full, wait and display surface.
// loop for interlace // loop for interlace
while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) { while (atomic_read(&decoder->SurfacesFilled) >= VIDEO_SURFACES_MAX) {
@@ -7474,7 +7562,7 @@ static void VdpauSyncRenderFrame(VdpauDecoder * decoder,
static int64_t VdpauGetClock(const VdpauDecoder * decoder) static int64_t VdpauGetClock(const VdpauDecoder * decoder)
{ {
// pts is the timestamp of the latest decoded frame // pts is the timestamp of the latest decoded frame
if (!decoder || (uint64_t) decoder->PTS == AV_NOPTS_VALUE) { if (!decoder || decoder->PTS == (int64_t) AV_NOPTS_VALUE) {
return AV_NOPTS_VALUE; return AV_NOPTS_VALUE;
} }
// subtract buffered decoded frames // subtract buffered decoded frames
@@ -7536,7 +7624,16 @@ static int VdpauPreemptionRecover(void)
} }
/// ///
/// Set VA-API video mode. /// Set VDPAU background color.
///
/// @param rgba 32 bit RGBA color.
///
static void VdpauSetBackground( __attribute__ ((unused)) uint32_t rgba)
{
}
///
/// Set VDPAU video mode.
/// ///
static void VdpauSetVideoMode(void) static void VdpauSetVideoMode(void)
{ {
@@ -7876,6 +7973,7 @@ static const VideoModule VdpauModule = {
.RenderFrame = (void (*const) (VideoHwDecoder *, .RenderFrame = (void (*const) (VideoHwDecoder *,
const AVCodecContext *, const AVFrame *))VdpauSyncRenderFrame, const AVCodecContext *, const AVFrame *))VdpauSyncRenderFrame,
.GrabOutput = VdpauGrabOutputSurface, .GrabOutput = VdpauGrabOutputSurface,
.SetBackground = VdpauSetBackground,
.SetVideoMode = VdpauSetVideoMode, .SetVideoMode = VdpauSetVideoMode,
.ResetAutoCrop = VdpauResetAutoCrop, .ResetAutoCrop = VdpauResetAutoCrop,
.Thread = VdpauDisplayHandlerThread, .Thread = VdpauDisplayHandlerThread,
@@ -8194,6 +8292,37 @@ static void VideoDisplayFrame(void)
/// C callback feed key press /// C callback feed key press
extern void FeedKeyPress(const char *, const char *, int, int); extern void FeedKeyPress(const char *, const char *, int, int);
///
/// Handle XLib I/O Errors.
///
/// @param display display with i/o error
///
static int VideoIOErrorHandler( __attribute__ ((unused)) Display * display)
{
Error(_("video: fatal i/o error\n"));
// should be called from VideoThread
if (VideoThread && VideoThread == pthread_self()) {
Debug(3, "video: called from video thread\n");
VideoUsedModule = NULL; // FIXME: NoopModule;
XlibDisplay = NULL;
VideoWindow = XCB_NONE;
#ifdef USE_VIDEO_THREAD
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_cond_destroy(&VideoWakeupCond);
pthread_mutex_destroy(&VideoLockMutex);
pthread_mutex_destroy(&VideoMutex);
VideoThread = 0;
pthread_exit("video thread exit");
#endif
}
do {
sleep(1000);
} while (1); // let other threads running
return -1;
}
/// ///
/// Handle X11 events. /// Handle X11 events.
/// ///
@@ -8503,6 +8632,7 @@ void VideoReleaseSurface(VideoHwDecoder * decoder, unsigned surface)
enum PixelFormat Video_get_format(VideoHwDecoder * decoder, enum PixelFormat Video_get_format(VideoHwDecoder * decoder,
AVCodecContext * video_ctx, const enum PixelFormat *fmt) AVCodecContext * video_ctx, const enum PixelFormat *fmt)
{ {
AudioVideoReady();
if (VideoUsedModule) { if (VideoUsedModule) {
return VideoUsedModule->get_format(decoder, video_ctx, fmt); return VideoUsedModule->get_format(decoder, video_ctx, fmt);
} }
@@ -8519,8 +8649,9 @@ enum PixelFormat Video_get_format(VideoHwDecoder * decoder,
void VideoRenderFrame(VideoHwDecoder * decoder, void VideoRenderFrame(VideoHwDecoder * decoder,
const AVCodecContext * video_ctx, const AVFrame * frame) const AVCodecContext * video_ctx, const AVFrame * frame)
{ {
if (frame->repeat_pict) { if (frame->repeat_pict && !VideoIgnoreRepeatPict) {
Warning(_("video: repeated pict found, but not handled\n")); Warning(_("video: repeated pict %d found, but not handled\n"),
frame->repeat_pict);
} }
if (VideoUsedModule) { if (VideoUsedModule) {
VideoUsedModule->RenderFrame(decoder, video_ctx, frame); VideoUsedModule->RenderFrame(decoder, video_ctx, frame);
@@ -8596,7 +8727,6 @@ void VideoDrawRenderState(VideoHwDecoder * hw_decoder,
return; return;
} }
Error(_("video/vdpau: draw render state, without vdpau enabled\n")); Error(_("video/vdpau: draw render state, without vdpau enabled\n"));
return;
} }
#endif #endif
@@ -8898,6 +9028,17 @@ int VideoSetGeometry(const char *geometry)
return 0; return 0;
} }
/// Set 60hz display mode.
///
/// Pull up 50 Hz video for 60 Hz display.
///
/// @param onoff enable / disable the 60 Hz mode.
///
void VideoSet60HzMode(int onoff)
{
Video60HzMode = onoff;
}
/// ///
/// Set video output position. /// Set video output position.
/// ///
@@ -9018,6 +9159,10 @@ void VideoSetFullscreen(int onoff)
{ {
xcb_client_message_event_t event; xcb_client_message_event_t event;
if (!XlibDisplay) { // needs running connection
return;
}
memset(&event, 0, sizeof(event)); memset(&event, 0, sizeof(event));
event.response_type = XCB_CLIENT_MESSAGE; event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32; event.format = 32;
@@ -9134,6 +9279,19 @@ void VideoSetStudioLevels(int onoff)
VideoStudioLevels = onoff; VideoStudioLevels = onoff;
} }
///
/// Set background color.
///
/// @param rgba 32 bit RGBA color.
///
void VideoSetBackground(uint32_t rgba)
{
VideoBackground = rgba; // save for later start
if (VideoUsedModule) {
VideoUsedModule->SetBackground(rgba);
}
}
/// ///
/// Set audio delay. /// Set audio delay.
/// ///
@@ -9198,6 +9356,9 @@ void VideoInit(const char *display_name)
return; return;
} }
// XInitThreads(); // XInitThreads();
// Register error handler
XSetIOErrorHandler(VideoIOErrorHandler);
// Convert XLIB display to XCB connection // Convert XLIB display to XCB connection
if (!(Connection = XGetXCBConnection(XlibDisplay))) { if (!(Connection = XGetXCBConnection(XlibDisplay))) {
Error(_("video: Can't convert XLIB display to XCB connection\n")); Error(_("video: Can't convert XLIB display to XCB connection\n"));
@@ -9318,6 +9479,7 @@ void VideoExit(void)
if (VideoUsedModule) { if (VideoUsedModule) {
VideoUsedModule->Exit(); VideoUsedModule->Exit();
} }
VideoUsedModule = NULL; // FIXME: NoopModule;
#ifdef USE_GLX #ifdef USE_GLX
if (GlxEnabled) { if (GlxEnabled) {
GlxExit(); GlxExit();

13
video.h
View File

@@ -30,6 +30,12 @@
/// Video hardware decoder typedef /// Video hardware decoder typedef
typedef struct _video_hw_decoder_ VideoHwDecoder; typedef struct _video_hw_decoder_ VideoHwDecoder;
//----------------------------------------------------------------------------
// Variables
//----------------------------------------------------------------------------
extern char VideoIgnoreRepeatPict; ///< disable repeat pict warning
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Prototypes // Prototypes
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -74,6 +80,9 @@ extern void VideoDisplayWakeup(void);
/// Set video geometry. /// Set video geometry.
extern int VideoSetGeometry(const char *); extern int VideoSetGeometry(const char *);
/// Set 60Hz display mode.
extern void VideoSet60HzMode(int);
/// Set video output position. /// Set video output position.
extern void VideoSetOutputPosition(int, int, int, int); extern void VideoSetOutputPosition(int, int, int, int);
@@ -110,6 +119,9 @@ extern void VideoSetSkipLines(int);
/// Set studio levels. /// Set studio levels.
extern void VideoSetStudioLevels(int); extern void VideoSetStudioLevels(int);
/// Set background.
extern void VideoSetBackground(uint32_t);
/// Set audio delay. /// Set audio delay.
extern void VideoSetAudioDelay(int); extern void VideoSetAudioDelay(int);
@@ -138,5 +150,6 @@ extern void VideoExit(void); ///< Cleanup and exit video module.
extern void VideoFlushInput(void); ///< Flush video input buffers. extern void VideoFlushInput(void); ///< Flush video input buffers.
extern int VideoDecode(void); ///< Decode video input buffers. extern int VideoDecode(void); ///< Decode video input buffers.
extern int VideoGetBuffers(void); ///< Get number of input buffers.
/// @} /// @}