summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--README.txt25
-rw-r--r--audio.c1182
-rw-r--r--audio.h4
-rw-r--r--codec.c10
-rw-r--r--softhddev.c24
-rw-r--r--softhddevice.cpp129
-rw-r--r--video.c59
-rw-r--r--video.h3
9 files changed, 1316 insertions, 121 deletions
diff --git a/ChangeLog b/ChangeLog
index 5dfb2d6..e6d0ecb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
User johns
Date:
+ Show black picture, if no video stream is available.
Setup split into foldable sections.
Adds show cursor on pointer move and hide after 200ms.
Adds Hot-key support for auto-crop enable/disable/toggle.
diff --git a/README.txt b/README.txt
index 28d39d7..82e6f53 100644
--- a/README.txt
+++ b/README.txt
@@ -157,6 +157,28 @@ Setup: /etc/vdr/setup.conf
0 = none, 1 = downmix
downmix AC-3 to stero.
+ softhddevice.AudioSoftvol
+ FIXME:
+
+ softhddevice.AudioNormalize
+ FIXME:
+
+ softhddevice.AudioMaxNormalize
+ FIXME:
+
+ softhddevice.AudioCompression
+ FIXME:
+
+ softhddevice.AudioMaxCompression
+ FIXME:
+
+ softhddevice.AudioStereoDescent
+ FIXME:
+
+ softhddevice.AudioBufferTime
+ FIXME:
+
+
softhddevice.AutoCrop.Interval = 0
0 disables auto-crop
n each 'n' frames auto-crop is checked.
@@ -253,6 +275,9 @@ Keymacros:
@softhddevice Blue 2 0 disable fullscreen
@softhddevice Blue 2 1 enable fullscreen
@softhddevice Blue 2 2 toggle fullscreen
+ @softhddevice Blue 2 3 disable auto-crop
+ @softhddevice Blue 2 4 enable auto-crop
+ @softhddevice Blue 2 5 toggle auto-crop
@softhddevice Blue 3 0 stretch 4:3 to 16:9
@softhddevice Blue 3 1 letter box 4:3 in 16:9
@softhddevice Blue 3 2 center cut-out 4:3 to 16:9
diff --git a/audio.c b/audio.c
index 96cee61..02c9e07 100644
--- a/audio.c
+++ b/audio.c
@@ -105,7 +105,7 @@ typedef struct _audio_module_
{
const char *Name; ///< audio output module name
- void (*const Thread) (void); ///< module thread handler
+ int (*const Thread) (void); ///< module thread handler
void (*const Enqueue) (const void *, int); ///< enqueue samples for output
void (*const VideoReady) (void); ///< video ready, start audio
void (*const FlushBuffers) (void); ///< flush sample buffers
@@ -140,10 +140,13 @@ static const char *AudioMixerChannel; ///< alsa/OSS mixer channel name
static volatile char AudioRunning; ///< thread running / stopped
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 const int AudioBytesProSample = 2; ///< number of bytes per sample
static int64_t AudioPTS; ///< audio pts clock
+
+#ifndef USE_AUDIO_THREAD
+#endif
static int AudioBufferTime = 336; ///< audio buffer time in ms
#ifdef USE_AUDIO_THREAD
@@ -154,15 +157,221 @@ static pthread_cond_t AudioStartCond; ///< condition variable
static const int AudioThread; ///< dummy audio thread
#endif
+static char AudioSoftVolume; ///< flag use soft volume
+static char AudioNormalize; ///< flag use volume normalize
+static char AudioCompression; ///< flag use compress volume
+static char AudioMute; ///< flag muted
+static int AudioAmplifier; ///< software volume factor
+static int AudioMaxNormalize; ///< max. normalize factor
+static int AudioCompressionFactor; ///< current compression factor
+static int AudioMaxCompression; ///< max. compression factor
+static int AudioStereoDescent; ///< volume descent for stereo
+static int AudioVolume; ///< volume (0 .. 1000)
+
extern int VideoAudioDelay; ///< import audio/video delay
+ /// default ring buffer size ~2s 8ch 16bit
+static const unsigned AudioRingBufferSize = 2 * 48000 * 8 * 2;
+
+static int AudioChannelsInHw[8]; ///< table which channels are supported
+enum _audio_rates
+{ ///< sample rates enumeration
+ // HW: 32000 44100 48000 88200 96000 176400 192000
+ //Audio32000, ///< 32.0Khz
+ Audio44100, ///< 44.1Khz
+ Audio48000, ///< 48.0Khz
+ //Audio88200, ///< 88.2Khz
+ //Audio96000, ///< 96.0Khz
+ //Audio176400, ///< 176.4Khz
+ //Audio192000, ///< 192.0Khz
+ AudioRatesMax ///< max index
+};
+static int AudioRatesInHw[AudioRatesMax]; ///< table which rates are supported
+
#ifdef USE_AUDIORING
//----------------------------------------------------------------------------
-// ring buffer
+// filter
//----------------------------------------------------------------------------
-// FIXME: use this code, to combine alsa&OSS ring buffers
+static const int AudioNormSamples = 32768; ///< number of samples
+
+#define AudioNormIndexes 128 ///< number of average values
+ /// average of n last sample blocks
+static uint32_t AudioNormAverage[AudioNormIndexes];
+static int AudioNormIndex; ///< index into average table
+static int AudioNormCounter; ///< sample counter
+
+/**
+** Audio normalizer.
+**
+** @param samples sample buffer
+** @param count number of bytes in sample buffer
+*/
+static void AudioNormalizer(int16_t * samples, int count)
+{
+ int i;
+ int n;
+ uint32_t avg;
+
+ // average samples
+ avg = 0;
+ n = count / AudioBytesProSample;
+ for (i = 0; i < n; ++i) {
+ int t;
+
+ t = samples[i];
+ avg += t * t;
+ avg /= 2;
+ }
+
+ // FIXME: more todo
+}
+
+/**
+** Reset normalizer.
+*/
+static void AudioResetNormalizer(void)
+{
+}
+
+/**
+** Audio compression.
+**
+** @param samples sample buffer
+** @param count number of bytes in sample buffer
+*/
+static void AudioCompressor(int16_t * samples, int count)
+{
+ int max_sample;
+ int i;
+ int factor;
+
+ // find loudest sample
+ max_sample = 0;
+ for (i = 0; i < count / AudioBytesProSample; ++i) {
+ int t;
+
+ t = abs(samples[i]);
+ if (t > max_sample) {
+ max_sample = t;
+ }
+ }
+
+ // calculate compression factor
+ if (max_sample > 0) {
+ factor = (INT16_MAX * 1000) / max_sample;
+ // smooth compression (FIXME: make configurable?)
+ AudioCompressionFactor =
+ (AudioCompressionFactor * 950 + factor * 50) / 1000;
+ if (AudioCompressionFactor > factor) {
+ AudioCompressionFactor = factor; // no clipping
+ }
+ if (AudioCompressionFactor > AudioMaxCompression) {
+ AudioCompressionFactor = AudioMaxCompression;
+ }
+ } else {
+ return; // silent nothing todo
+ }
+
+ Debug(4, "audio/compress: max %5d, fac=%6.3f, com=%6.3f\n", max_sample,
+ factor / 1000.0, AudioCompressionFactor / 1000.0);
+
+ // apply compression factor
+ for (i = 0; i < count / AudioBytesProSample; ++i) {
+ int t;
+
+ t = (samples[i] * AudioCompressionFactor) / 1000;
+ if (t < INT16_MIN) {
+ t = INT16_MIN;
+ } else if (t > INT16_MAX) {
+ t = INT16_MAX;
+ }
+ samples[i] = t;
+ }
+}
+
+/**
+** Reset compressor.
+*/
+static void AudioResetCompressor(void)
+{
+ AudioCompressionFactor = 2000;
+ if (AudioCompressionFactor > AudioMaxCompression) {
+ AudioCompressionFactor = AudioMaxCompression;
+ }
+}
+
+/**
+** Audio software amplifier.
+**
+** @param samples sample buffer
+** @param count number of bytes in sample buffer
+**
+** @todo FIXME: this does hard clipping
+*/
+static void AudioSoftAmplifier(int16_t * samples, int count)
+{
+ int i;
+
+ // silence
+ if (AudioMute || !AudioAmplifier) {
+ memset(samples, 0, count);
+ return;
+ }
+
+ for (i = 0; i < count / AudioBytesProSample; ++i) {
+ int t;
+
+ t = (samples[i] * AudioAmplifier) / 1000;
+ if (t < INT16_MIN) {
+ t = INT16_MIN;
+ } else if (t > INT16_MAX) {
+ t = INT16_MAX;
+ }
+ samples[i] = t;
+ }
+}
+
+/**
+** Upmix mono to stereo.
+**
+** @param in input sample buffer
+** @param count number of bytes in sample buffer
+** @param out output sample buffer
+*/
+static void AudioMono2Stereo(const int16_t * in, int count, int16_t * out)
+{
+ int i;
+
+ for (i = 0; i < count / AudioBytesProSample; ++i) {
+ int t;
+
+ t = in[i];
+ out[i * 2 + 0] = t;
+ out[i * 2 + 1] = t;
+ }
+}
+
+/**
+** Downmix stereo to mono.
+**
+** @param in input sample buffer
+** @param count number of bytes in sample buffer
+** @param out output sample buffer
+*/
+static void AudioStereo2Mono(const int16_t * in, int count, int16_t * out)
+{
+ int i;
+
+ for (i = 0; i < count / (2 * AudioBytesProSample); ++i) {
+ out[i] = (in[i * 2 + 0] + in[i * 2 + 1]) / 2;
+ }
+}
+
+//----------------------------------------------------------------------------
+// ring buffer
+//----------------------------------------------------------------------------
#define AUDIO_RING_MAX 8 ///< number of audio ring buffers
@@ -172,41 +381,57 @@ extern int VideoAudioDelay; ///< import audio/video delay
typedef struct _audio_ring_ring_
{
char FlushBuffers; ///< flag: flush buffers
- unsigned SampleRate; ///< sample rate in hz
- unsigned Channels; ///< number of channels
+ char UseAc3; ///< flag: use ac3 pass-through
+ unsigned HwSampleRate; ///< hardware sample rate in Hz
+ unsigned HwChannels; ///< hardware number of channels
+ unsigned InSampleRate; ///< input sample rate in Hz
+ unsigned InChannels; ///< input number of channels
+ int64_t PTS; ///< pts clock
+ RingBuffer *RingBuffer; ///< sample ring buffer
} AudioRingRing;
+ /// default ring buffer size ~2s 8ch 16bit
+//static const unsigned AudioRingBufferSize = 2 * 48000 * 8 * 2;
+
/// ring of audio ring buffers
static AudioRingRing AudioRing[AUDIO_RING_MAX];
static int AudioRingWrite; ///< audio ring write pointer
static int AudioRingRead; ///< audio ring read pointer
static atomic_t AudioRingFilled; ///< how many of the ring is used
+static unsigned AudioStartThreshold; ///< start play, if filled
/**
** Add sample rate, number of channel change to ring.
**
** @param freq sample frequency
** @param channels number of channels
+** @param use_ac3 use ac3/pass-through device
+**
+** @retval -1 error
+** @retval 0 okay
*/
-static int AudioRingAdd(int freq, int channels)
+static int AudioRingAdd(int freq, int channels, int use_ac3)
{
- int filled;
-
- filled = atomic_read(&AudioRingFilled);
- if (filled == AUDIO_RING_MAX) { // no free slot
+ if (atomic_read(&AudioRingFilled) == AUDIO_RING_MAX) { // no free slot
// FIXME: can wait for ring buffer empty
Error(_("audio: out of ring buffers\n"));
return -1;
}
+ AudioRingWrite = (AudioRingWrite + 1) % AUDIO_RING_MAX;
+
+ // FIXME: don't flush buffers here
AudioRing[AudioRingWrite].FlushBuffers = 1;
- AudioRing[AudioRingWrite].SampleRate = freq;
- AudioRing[AudioRingWrite].Channels = channels;
+ AudioRing[AudioRingWrite].UseAc3 = use_ac3;
+ AudioRing[AudioRingWrite].HwSampleRate = freq;
+ AudioRing[AudioRingWrite].HwChannels = channels;
+ AudioRing[AudioRingWrite].PTS = INT64_C(0x8000000000000000);
+ RingBufferReadAdvance(AudioRing[AudioRingWrite].RingBuffer,
+ RingBufferUsedBytes(AudioRing[AudioRingWrite].RingBuffer));
- AudioRingWrite = (AudioRingWrite + 1) % AUDIO_RING_MAX;
atomic_inc(&AudioRingFilled);
#ifdef USE_AUDIO_THREAD
- // tell thread, that something todo
+ // tell thread, that there is something todo
AudioRunning = 1;
pthread_cond_signal(&AudioStartCond);
#endif
@@ -222,12 +447,10 @@ static void AudioRingInit(void)
int i;
for (i = 0; i < AUDIO_RING_MAX; ++i) {
- // FIXME:
- //AlsaRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
+ // ~2s 8ch 16bit
+ AudioRing[i].RingBuffer = RingBufferNew(AudioRingBufferSize);
}
- // one slot always reservered
- AudioRingWrite = 1;
- atomic_set(&AudioRingFilled, 1);
+ atomic_set(&AudioRingFilled, 0);
}
/**
@@ -238,9 +461,14 @@ static void AudioRingExit(void)
int i;
for (i = 0; i < AUDIO_RING_MAX; ++i) {
- // FIXME:
- //RingBufferDel(AlsaRingBuffer);
+ if (AudioRing[i].RingBuffer) {
+ RingBufferDel(AudioRing[i].RingBuffer);
+ AudioRing[i].RingBuffer = NULL;
+ }
+ AudioRing[i].HwSampleRate = 0; // checked for valid setup
}
+ AudioRingRead = 0;
+ AudioRingWrite = 0;
}
#endif
@@ -259,6 +487,8 @@ static snd_pcm_t *AlsaPCMHandle; ///< alsa pcm handle
static char AlsaCanPause; ///< hw supports pause
static int AlsaUseMmap; ///< use mmap
+#ifndef USE_AUDIORING
+
static RingBuffer *AlsaRingBuffer; ///< audio ring buffer
static unsigned AlsaStartThreshold; ///< start play, if filled
@@ -266,10 +496,167 @@ static unsigned AlsaStartThreshold; ///< start play, if filled
static volatile char AlsaFlushBuffer; ///< flag empty buffer
#endif
+#endif
+
static snd_mixer_t *AlsaMixer; ///< alsa mixer handle
static snd_mixer_elem_t *AlsaMixerElem; ///< alsa pcm mixer element
static int AlsaRatio; ///< internal -> mixer ratio * 1000
+#ifdef USE_AUDIORING
+
+//----------------------------------------------------------------------------
+// alsa pcm
+//----------------------------------------------------------------------------
+
+/**
+** Play samples from ringbuffer.
+**
+** Fill the kernel buffer, as much as possible.
+**
+** @retval 0 ok
+** @retval 1 ring buffer empty
+** @retval -1 underrun error
+*/
+static int AlsaPlayRingbuffer(void)
+{
+ int first;
+ int avail;
+ int n;
+ int err;
+ int frames;
+ const void *p;
+
+ first = 1;
+ for (;;) { // loop for ring buffer wrap
+ // how many bytes can be written?
+ n = snd_pcm_avail_update(AlsaPCMHandle);
+ if (n < 0) {
+ if (n == -EAGAIN) {
+ continue;
+ }
+ Warning(_("audio/alsa: avail underrun error? '%s'\n"),
+ snd_strerror(n));
+ err = snd_pcm_recover(AlsaPCMHandle, n, 0);
+ if (err >= 0) {
+ continue;
+ }
+ Error(_("audio/alsa: snd_pcm_avail_update(): %s\n"),
+ snd_strerror(n));
+ return -1;
+ }
+ avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, n);
+ if (avail < 256) { // too much overhead
+ if (first) {
+ // happens with broken alsa drivers
+ if (AudioThread) {
+ if (!AudioAlsaDriverBroken) {
+ Error(_("audio/alsa: broken driver %d state '%s'\n"),
+ avail,
+ snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
+ }
+ // try to recover
+ 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);
+ }
+ }
+ Debug(4, "audio/alsa: break state '%s'\n",
+ snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
+ break;
+ }
+
+ n = RingBufferGetReadPointer(AudioRing[AudioRingRead].RingBuffer, &p);
+ if (!n) { // ring buffer empty
+ if (first) { // only error on first loop
+ Debug(4, "audio/alsa: empty buffers %d\n", avail);
+ // ring buffer empty
+ // AlsaLowWaterMark = 1;
+ return 1;
+ }
+ return 0;
+ }
+ if (n < avail) { // not enough bytes in ring buffer
+ avail = n;
+ }
+ if (!avail) { // full or buffer empty
+ break;
+ }
+ if (AudioSoftVolume) {
+ // FIXME: quick&dirty cast
+ AudioSoftAmplifier((int16_t *) p, avail);
+ }
+ frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
+
+ for (;;) {
+ if (AlsaUseMmap) {
+ err = snd_pcm_mmap_writei(AlsaPCMHandle, p, frames);
+ } else {
+ err = snd_pcm_writei(AlsaPCMHandle, p, frames);
+ }
+ //Debug(3, "audio/alsa: wrote %d/%d frames\n", err, frames);
+ if (err != frames) {
+ if (err < 0) {
+ if (err == -EAGAIN) {
+ continue;
+ }
+ /*
+ if (err == -EBADFD) {
+ goto again;
+ }
+ */
+ Warning(_("audio/alsa: writei underrun error? '%s'\n"),
+ snd_strerror(err));
+ err = snd_pcm_recover(AlsaPCMHandle, err, 0);
+ if (err >= 0) {
+ continue;
+ }
+ Error(_("audio/alsa: snd_pcm_writei failed: %s\n"),
+ snd_strerror(err));
+ return -1;
+ }
+ // this could happen, if underrun happened
+ Warning(_("audio/alsa: not all frames written\n"));
+ avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, err);
+ }
+ break;
+ }
+ RingBufferReadAdvance(AudioRing[AudioRingRead].RingBuffer, avail);
+ first = 0;
+ }
+
+ return 0;
+}
+
+/**
+** Flush alsa buffers.
+*/
+static void AlsaFlushBuffers(void)
+{
+ if (AlsaPCMHandle) {
+ int err;
+ snd_pcm_state_t state;
+
+ state = snd_pcm_state(AlsaPCMHandle);
+ Debug(3, "audio/alsa: flush state %s\n", snd_pcm_state_name(state));
+ if (state != SND_PCM_STATE_OPEN) {
+ if ((err = snd_pcm_drop(AlsaPCMHandle)) < 0) {
+ Error(_("audio: snd_pcm_drop(): %s\n"), snd_strerror(err));
+ }
+ // ****ing alsa crash, when in open state here
+ if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
+ Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err));
+ }
+ }
+ }
+}
+
+#else
+
//----------------------------------------------------------------------------
// alsa pcm
//----------------------------------------------------------------------------
@@ -645,16 +1032,81 @@ static void AlsaEnqueue(const void *samples, int count)
#endif
+#endif
+
#ifdef USE_AUDIO_THREAD
//----------------------------------------------------------------------------
// thread playback
//----------------------------------------------------------------------------
+#ifdef USE_AUDIORING
+
+/**
+** Alsa thread
+**
+** Play some samples and return.
+**
+** @retval -1 error
+** @retval 0 underrun
+** @retval 1 running
+*/
+static int AlsaThread(void)
+{
+ int err;
+
+ if (!AlsaPCMHandle) {
+ usleep(24 * 1000);
+ return -1;
+ }
+ for (;;) {
+ pthread_testcancel();
+ if (AudioPaused) {
+ return 1;
+ }
+ // wait for space in kernel buffers
+ if ((err = snd_pcm_wait(AlsaPCMHandle, 24)) < 0) {
+ Warning(_("audio/alsa: wait underrun error? '%s'\n"),
+ snd_strerror(err));
+ err = snd_pcm_recover(AlsaPCMHandle, err, 0);
+ if (err >= 0) {
+ continue;
+ }
+ Error(_("audio/alsa: snd_pcm_wait(): %s\n"), snd_strerror(err));
+ usleep(24 * 1000);
+ return -1;
+ }
+ break;
+ }
+ if (!err || AudioPaused) { // timeout or some commands
+ return 1;
+ }
+
+ if ((err = AlsaPlayRingbuffer())) { // empty or error
+ snd_pcm_state_t state;
+
+ if (err < 0) { // underrun error
+ return -1;
+ }
+
+ state = snd_pcm_state(AlsaPCMHandle);
+ if (state != SND_PCM_STATE_RUNNING) {
+ Debug(3, "audio/alsa: stopping play '%s'\n",
+ snd_pcm_state_name(state));
+ return 0;
+ }
+
+ usleep(24 * 1000); // let fill/empty the buffers
+ }
+ return 1;
+}
+
+#else
+
/**
** Alsa thread
*/
-static void AlsaThread(void)
+static int AlsaThread(void)
{
for (;;) {
int err;
@@ -707,6 +1159,7 @@ static void AlsaThread(void)
usleep(24 * 1000); // let fill/empty the buffers
}
}
+ return 0;
}
/**
@@ -783,6 +1236,8 @@ static void AlsaThreadFlushBuffers(void)
#endif
+#endif
+
//----------------------------------------------------------------------------
/**
@@ -831,12 +1286,10 @@ static void AlsaInitPCM(void)
snd_pcm_hw_params_t *hw_params;
int err;
- //snd_pcm_uframes_t buffer_size;
-
if (!(handle = AlsaOpenPCM(0))) {
return;
}
-
+ // FIXME: pass-through and pcm out can support different features
snd_pcm_hw_params_alloca(&hw_params);
// choose all parameters
if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
@@ -846,9 +1299,6 @@ static void AlsaInitPCM(void)
}
AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params);
Info(_("audio/alsa: supports pause: %s\n"), AlsaCanPause ? "yes" : "no");
- // needs audio setup
- //snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
- //Info(_("audio/alsa: max buffer size %lu\n"), buffer_size);
AlsaPCMHandle = handle;
}
@@ -858,16 +1308,16 @@ static void AlsaInitPCM(void)
//----------------------------------------------------------------------------
/**
-** Set alsa mixer volume (0-100)
+** Set alsa mixer volume (0-1000)
**
-** @param volume volume (0 .. 100)
+** @param volume volume (0 .. 1000)
*/
static void AlsaSetVolume(int volume)
{
int v;
if (AlsaMixer && AlsaMixerElem) {
- v = (volume * AlsaRatio) / 1000;
+ v = (volume * AlsaRatio) / (1000 * 1000);
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 0, v);
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 1, v);
}
@@ -911,8 +1361,7 @@ static void AlsaInitMixer(void)
if (!strcasecmp(name, alsa_mixer_elem_name)) {
snd_mixer_selem_get_playback_volume_range(alsa_mixer_elem,
&alsa_mixer_elem_min, &alsa_mixer_elem_max);
- AlsaRatio =
- (1000 * (alsa_mixer_elem_max - alsa_mixer_elem_min)) / 100;
+ AlsaRatio = 1000 * (alsa_mixer_elem_max - alsa_mixer_elem_min);
Debug(3, "audio/alsa: PCM mixer found %ld - %ld ratio %d\n",
alsa_mixer_elem_min, alsa_mixer_elem_max, AlsaRatio);
break;
@@ -932,6 +1381,169 @@ static void AlsaInitMixer(void)
// Alsa API
//----------------------------------------------------------------------------
+#ifdef USE_AUDIORING
+
+/**
+** Get alsa audio delay in time-stamps.
+**
+** @returns audio delay in time-stamps.
+**
+** @todo FIXME: handle the case no audio running
+*/
+static int64_t AlsaGetDelay(void)
+{
+ int err;
+ snd_pcm_sframes_t delay;
+ int64_t pts;
+
+ if (!AlsaPCMHandle) { // setup error
+ return 0L;
+ }
+ // delay in frames in alsa + kernel buffers
+ if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) {
+ //Debug(3, "audio/alsa: no hw delay\n");
+ delay = 0L;
+ } else if (snd_pcm_state(AlsaPCMHandle) != SND_PCM_STATE_RUNNING) {
+ //Debug(3, "audio/alsa: %ld frames delay ok, but not running\n", delay);
+ }
+ //Debug(3, "audio/alsa: %ld frames hw delay\n", delay);
+
+ // delay can be negative when underrun occur
+ if (delay < 0) {
+ delay = 0L;
+ }
+
+ pts =
+ ((int64_t) delay * 90 * 1000) / AudioRing[AudioRingRead].HwSampleRate;
+
+ return pts;
+}
+
+/**
+** Setup alsa audio for requested format.
+**
+** @param freq sample frequency
+** @param channels number of channels
+** @param use_ac3 use ac3/pass-through device
+**
+** @retval 0 everything ok
+** @retval 1 didn't support frequency/channels combination
+** @retval -1 something gone wrong
+**
+** @todo FIXME: remove pointer for freq + channels
+*/
+static int AlsaSetup(int *freq, int *channels, int use_ac3)
+{
+ snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t period_size;
+ int err;
+ int delay;
+
+ if (!AlsaPCMHandle) { // alsa not running yet
+ return -1;
+ }
+ if (1) { // close+open to fix HDMI no sound bug
+ snd_pcm_t *handle;
+
+ handle = AlsaPCMHandle;
+ // FIXME: need lock
+ AlsaPCMHandle = NULL; // other threads should check handle
+ snd_pcm_close(handle);
+ if (!(handle = AlsaOpenPCM(use_ac3))) {
+ return -1;
+ }
+ AlsaPCMHandle = handle;
+ }
+
+ for (;;) {
+ if ((err =
+ snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
+ AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
+ SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1,
+ 96 * 1000))) {
+
+ /*
+ if ( err == -EBADFD ) {
+ snd_pcm_close(AlsaPCMHandle);
+ AlsaPCMHandle = NULL;
+ continue;
+ }
+ */
+
+ Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
+ // FIXME: must stop sound, AudioChannels ... invalid
+ return -1;
+ }
+ break;
+ }
+
+ // this is disabled, no advantages!
+ if (0) { // no underruns allowed, play silence
+ snd_pcm_sw_params_t *sw_params;
+ snd_pcm_uframes_t boundary;
+
+ snd_pcm_sw_params_alloca(&sw_params);
+ err = snd_pcm_sw_params_current(AlsaPCMHandle, sw_params);
+ if (err < 0) {
+ Error(_("audio: snd_pcm_sw_params_current failed: %s\n"),
+ snd_strerror(err));
+ }
+ if ((err = snd_pcm_sw_params_get_boundary(sw_params, &boundary)) < 0) {
+ Error(_("audio: snd_pcm_sw_params_get_boundary failed: %s\n"),
+ snd_strerror(err));
+ }
+ Debug(4, "audio/alsa: boundary %lu frames\n", boundary);
+ if ((err =
+ snd_pcm_sw_params_set_stop_threshold(AlsaPCMHandle, sw_params,
+ boundary)) < 0) {
+ Error(_("audio: snd_pcm_sw_params_set_silence_size failed: %s\n"),
+ snd_strerror(err));
+ }
+ if ((err =
+ snd_pcm_sw_params_set_silence_size(AlsaPCMHandle, sw_params,
+ boundary)) < 0) {
+ Error(_("audio: snd_pcm_sw_params_set_silence_size failed: %s\n"),
+ snd_strerror(err));
+ }
+ if ((err = snd_pcm_sw_params(AlsaPCMHandle, sw_params)) < 0) {
+ Error(_("audio: snd_pcm_sw_params failed: %s\n"),
+ snd_strerror(err));
+ }
+ }
+ // update buffer
+
+ snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size);
+ Debug(3, "audio/alsa: buffer size %lu %zdms, period size %lu %zdms\n",
+ buffer_size, snd_pcm_frames_to_bytes(AlsaPCMHandle,
+ buffer_size) * 1000 / (*freq * *channels * AudioBytesProSample),
+ period_size, snd_pcm_frames_to_bytes(AlsaPCMHandle,
+ period_size) * 1000 / (*freq * *channels * AudioBytesProSample));
+ Debug(3, "audio/alsa: state %s\n",
+ snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
+
+ AudioStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, period_size);
+ // buffer time/delay in ms
+ delay = AudioBufferTime;
+ if (VideoAudioDelay > 0) {
+ delay += VideoAudioDelay / 90;
+ }
+ if (AudioStartThreshold <
+ (*freq * *channels * AudioBytesProSample * delay) / 1000U) {
+ AudioStartThreshold =
+ (*freq * *channels * AudioBytesProSample * delay) / 1000U;
+ }
+ // no bigger, than 1/3 the buffer
+ if (AudioStartThreshold > AudioRingBufferSize / 3) {
+ AudioStartThreshold = AudioRingBufferSize / 3;
+ }
+ Info(_("audio/alsa: start delay %ums\n"), (AudioStartThreshold * 1000)
+ / (*freq * *channels * AudioBytesProSample));
+
+ return 0;
+}
+
+#else
+
/**
** Get alsa audio delay in time stamps.
**
@@ -1208,6 +1820,8 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
return ret;
}
+#endif
+
/**
** Play audio.
*/
@@ -1272,7 +1886,9 @@ static void AlsaInit(void)
#else
(void)AlsaNoopCallback;
#endif
- AlsaRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
+#ifndef USE_AUDIORING
+ AlsaRingBuffer = RingBufferNew(AudioRingBufferSize);
+#endif
AlsaInitPCM();
AlsaInitMixer();
@@ -1292,11 +1908,13 @@ static void AlsaExit(void)
AlsaMixer = NULL;
AlsaMixerElem = NULL;
}
+#ifndef USE_AUDIORING
if (AlsaRingBuffer) {
RingBufferDel(AlsaRingBuffer);
AlsaRingBuffer = NULL;
}
AlsaFlushBuffer = 0;
+#endif
}
/**
@@ -1306,17 +1924,25 @@ static const AudioModule AlsaModule = {
.Name = "alsa",
#ifdef USE_AUDIO_THREAD
.Thread = AlsaThread,
+#ifdef USE_AUDIORING
+ //.Enqueue = AlsaThreadEnqueue,
+ //.VideoReady = AlsaVideoReady,
+ .FlushBuffers = AlsaFlushBuffers,
+#else
.Enqueue = AlsaThreadEnqueue,
.VideoReady = AlsaVideoReady,
.FlushBuffers = AlsaThreadFlushBuffers,
+#endif
#else
.Enqueue = AlsaEnqueue,
.VideoReady = AlsaVideoReady,
.FlushBuffers = AlsaFlushBuffers,
#endif
+#ifndef USE_AUDIORING
.Poller = AlsaPoller,
.FreeBytes = AlsaFreeBytes,
.UsedBytes = AlsaUsedBytes,
+#endif
.GetDelay = AlsaGetDelay,
.SetVolume = AlsaSetVolume,
.Setup = AlsaSetup,
@@ -1533,7 +2159,7 @@ static int OssUsedBytes(void)
/**
** OSS thread
*/
-static void OssThread(void)
+static int OssThread(void)
{
for (;;) {
struct pollfd fds[1];
@@ -1573,6 +2199,7 @@ static void OssThread(void)
usleep(OssFragmentTime * 1000); // let fill/empty the buffers
}
}
+ return 0;
}
/**
@@ -1679,16 +2306,16 @@ static void OssInitPCM(void)
//----------------------------------------------------------------------------
/**
-** Set OSS mixer volume (0-100)
+** Set OSS mixer volume (0-1000)
**
-** @param volume volume (0 .. 100)
+** @param volume volume (0 .. 1000)
*/
static void OssSetVolume(int volume)
{
int v;
if (OssMixerFildes != -1) {
- v = (volume * 255) / 100;
+ v = (volume * 255) / 1000;
v &= 0xff;
v = (v << 8) | v;
if (ioctl(OssMixerFildes, MIXER_WRITE(OssMixerChannel), &v) < 0) {
@@ -1863,7 +2490,7 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
return -1;
}
if (tmp != *freq) {
- Warning(_("audio/oss: device doesn't support %d Hz sample rate.\n"),
+ Warning(_("audio/oss: device doesn't support %dHz sample rate.\n"),
*freq);
*freq = tmp;
ret = 1;
@@ -1945,7 +2572,7 @@ void OssPause(void)
*/
static void OssInit(void)
{
- OssRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
+ OssRingBuffer = RingBufferNew(AudioRingBufferSize);
OssInitPCM();
OssInitMixer();
@@ -2039,9 +2666,9 @@ static int64_t NoopGetDelay(void)
}
/**
-** Set mixer volume (0-100)
+** Set mixer volume (0-1000)
**
-** @param volume volume (0 .. 100)
+** @param volume volume (0 .. 1000)
*/
static void NoopSetVolume( __attribute__ ((unused))
int volume)
@@ -2095,8 +2722,47 @@ static const AudioModule NoopModule = {
#ifdef USE_AUDIO_THREAD
+#ifdef USE_AUDIORING
+
+/**
+** Prepare next ring buffer.
+*/
+static int AudioNextRing(void)
+{
+ int use_ac3;
+ int sample_rate;
+ int channels;
+
+ // update audio format
+ // not always needed, but check if needed is too complex
+ use_ac3 = AudioRing[AudioRingRead].UseAc3;
+ sample_rate = AudioRing[AudioRingRead].HwSampleRate;
+ channels = AudioRing[AudioRingRead].HwChannels;
+ if (AudioUsedModule->Setup(&sample_rate, &channels, use_ac3)) {
+ Error(_("audio: can't set channels %d sample-rate %dHz\n"), channels,
+ sample_rate);
+ // FIXME: handle error
+ AudioRing[AudioRingRead].HwSampleRate = 0;
+ return -1;
+ }
+ AudioSetVolume(AudioVolume); // update channel delta
+ AudioResetCompressor();
+ AudioResetNormalizer();
+ AudioRing[AudioRingRead].HwSampleRate = sample_rate;
+ AudioRing[AudioRingRead].HwChannels = channels;
+
+ // stop if not enough in next buffer
+ if (AudioStartThreshold >=
+ RingBufferUsedBytes(AudioRing[AudioRingRead].RingBuffer)) {
+ return 1;
+ }
+ return 0;
+}
+
/**
** Audio play thread.
+**
+** @param dummy unused thread argument
*/
static void *AudioPlayHandlerThread(void *dummy)
{
@@ -2109,43 +2775,110 @@ static void *AudioPlayHandlerThread(void *dummy)
pthread_cond_wait(&AudioStartCond, &AudioMutex);
// cond_wait can return, without signal!
} while (!AudioRunning);
+ pthread_mutex_unlock(&AudioMutex);
Debug(3, "audio: ----> %dms start\n", (AudioUsedBytes() * 1000)
- / (!AudioSampleRate + !AudioChannels +
- AudioSampleRate * AudioChannels * AudioBytesProSample));
+ / (!AudioRing[AudioRingRead].HwSampleRate +
+ !AudioRing[AudioRingRead].HwChannels +
+ AudioRing[AudioRingRead].HwSampleRate *
+ AudioRing[AudioRingRead].HwChannels * AudioBytesProSample));
- pthread_mutex_unlock(&AudioMutex);
+ do {
+ int filled;
+ int read;
+ int flush;
+ int err;
+
+ // look if there is a flush command in the queue
+ flush = 0;
+ filled = atomic_read(&AudioRingFilled);
+ read = AudioRingRead;
+ while (filled--) {
+ read = (read + 1) % AUDIO_RING_MAX;
+ if (AudioRing[read].FlushBuffers) {
+ AudioRing[read].FlushBuffers = 0;
+ AudioRingRead = read;
+ atomic_set(&AudioRingFilled, filled);
+ // handle all flush in queue
+ flush = 1;
+ }
+ }
-#ifdef USE_AUDIORING
- if (atomic_read(&AudioRingFilled) > 1) {
- int sample_rate;
- int channels;
+ if (flush) {
+ AudioUsedModule->FlushBuffers();
+ if (AudioNextRing()) {
+ break;
+ }
+ }
+ // try to play some samples
+ err = AudioUsedModule->Thread();
+ // underrun, check if new ring buffer is available
+ if (!err) {
+ int use_ac3;
+ int sample_rate;
+ int channels;
+ int old_use_ac3;
+ int old_sample_rate;
+ int old_channels;
+
+ // underrun, and no new ring buffer, goto sleep.
+ if (!atomic_read(&AudioRingFilled)) {
+ break;
+ }
+
+ Debug(3, "audio: next ring buffer\n");
+ old_use_ac3 = AudioRing[AudioRingRead].UseAc3;
+ old_sample_rate = AudioRing[AudioRingRead].HwSampleRate;
+ old_channels = AudioRing[AudioRingRead].HwChannels;
- // skip all sample changes between
- while (atomic_read(&AudioRingFilled) > 1) {
- Debug(3, "audio: skip ring buffer\n");
- AudioRingRead = (AudioRingRead + 1) % AUDIO_RING_MAX;
atomic_dec(&AudioRingFilled);
+ AudioRingRead = (AudioRingRead + 1) % AUDIO_RING_MAX;
+
+ use_ac3 = AudioRing[AudioRingRead].UseAc3;
+ sample_rate = AudioRing[AudioRingRead].HwSampleRate;
+ channels = AudioRing[AudioRingRead].HwChannels;
+ Debug(3, "audio: thread channels %d frequency %dHz %s\n",
+ channels, sample_rate, use_ac3 ? "ac3" : "pcm");
+ // audio config changed?
+ if (old_use_ac3 != use_ac3 || old_sample_rate != sample_rate
+ || old_channels != channels) {
+ // FIXME: wait for buffer drain
+ if (AudioNextRing()) {
+ break;
+ }
+ } else {
+ AudioResetCompressor();
+ AudioResetNormalizer();
+ }
}
+ } while (AudioRing[AudioRingRead].HwSampleRate);
+ }
+ return dummy;
+}
-#ifdef USE_ALSA
- // FIXME: flush only if there is something to flush
- AlsaFlushBuffers();
+#else
- sample_rate = AudioRing[AudioRingRead].SampleRate;
- channels = AudioRing[AudioRingRead].Channels;
- Debug(3, "audio: thread channels %d sample-rate %d hz\n", channels,
- sample_rate);
+/**
+** Audio play thread.
+**
+** @param dummy unused thread argument
+*/
+static void *AudioPlayHandlerThread(void *dummy)
+{
+ Debug(3, "audio: play thread started\n");
+ for (;;) {
+ Debug(3, "audio: wait on start condition\n");
+ pthread_mutex_lock(&AudioMutex);
+ AudioRunning = 0;
+ do {
+ pthread_cond_wait(&AudioStartCond, &AudioMutex);
+ // cond_wait can return, without signal!
+ } while (!AudioRunning);
+ pthread_mutex_unlock(&AudioMutex);
- if (AlsaSetup(&sample_rate, &channels)) {
- Error(_("audio: can't set channels %d sample-rate %d hz\n"),
- channels, sample_rate);
- }
- Debug(3, "audio: thread channels %d sample-rate %d hz\n",
- AudioChannels, AudioSampleRate);
-#endif
- }
-#endif
+ Debug(3, "audio: ----> %dms start\n", (AudioUsedBytes() * 1000)
+ / (!AudioSampleRate + !AudioChannels +
+ AudioSampleRate * AudioChannels * AudioBytesProSample));
AudioUsedModule->Thread();
}
@@ -2153,6 +2886,8 @@ static void *AudioPlayHandlerThread(void *dummy)
return dummy;
}
+#endif
+
/**
** Initialize audio thread.
*/
@@ -2162,9 +2897,10 @@ static void AudioInitThread(void)
pthread_cond_init(&AudioStartCond, NULL);
pthread_create(&AudioThread, NULL, AudioPlayHandlerThread, NULL);
pthread_setname_np(AudioThread, "softhddev audio");
-
+#ifndef USE_AUDIORING
pthread_yield();
usleep(5 * 1000); // give thread some time to start
+#endif
}
/**
@@ -2213,6 +2949,68 @@ static const AudioModule *AudioModules[] = {
*/
void AudioEnqueue(const void *samples, int count)
{
+#ifdef USE_AUDIORING
+ size_t n;
+
+#ifdef DEBUG
+ static uint32_t last_tick;
+ uint32_t tick;
+
+ tick = GetMsTicks();
+ if (tick - last_tick > 101) {
+ Debug(3, "audio: enqueue %4d %dms\n", count, tick - last_tick);
+ }
+ last_tick = tick;
+#endif
+
+ if (!AudioRing[AudioRingWrite].HwSampleRate) {
+ Debug(3, "audio: enqueue not ready\n");
+ return; // no setup yet
+ }
+
+ if (AudioCompression) {
+ // FIXME: quick&dirty const cast
+ AudioCompressor((int16_t *) samples, count);
+ }
+ if (AudioNormalize) {
+ // FIXME: quick&dirty const cast
+ AudioNormalizer((int16_t *) samples, count);
+ }
+
+ n = RingBufferWrite(AudioRing[AudioRingWrite].RingBuffer, samples, count);
+ if (n != (size_t) count) {
+ Error(_("audio: can't place %d samples in ring buffer\n"), count);
+ // too many bytes are lost
+ // FIXME: caller checks buffer full.
+ // FIXME: should skip more, longer skip, but less often?
+ // FIXME: round to channel + sample border
+ }
+
+ if (!AudioRunning) { // check, if we can start the thread
+ //int64_t video_pts;
+
+ //video_pts = VideoGetClock();
+ n = RingBufferUsedBytes(AudioRing[AudioRingWrite].RingBuffer);
+ Debug(3, "audio: start? %4zdms\n", (n * 1000)
+ / (AudioRing[AudioRingWrite].HwSampleRate *
+ AudioRing[AudioRingWrite].HwChannels * AudioBytesProSample));
+
+ // forced start or enough video + audio buffered
+ if (AudioStartThreshold * 2 < n || (AudioVideoIsReady
+ && AudioStartThreshold < n)) {
+ // restart play-back
+ // no lock needed, can wakeup next time
+ AudioRunning = 1;
+ pthread_cond_signal(&AudioStartCond);
+ }
+ }
+ // Update audio clock (stupid gcc developers thinks INT64_C is unsigned)
+ if (AudioRing[AudioRingWrite].PTS != (int64_t) INT64_C(0x8000000000000000)) {
+ AudioRing[AudioRingWrite].PTS += ((int64_t) count * 90 * 1000)
+ / (AudioRing[AudioRingWrite].HwSampleRate *
+ AudioRing[AudioRingWrite].HwChannels * AudioBytesProSample);
+ }
+#else
if (!AudioSampleRate || !AudioChannels) {
return; // not setup
}
@@ -2239,15 +3037,59 @@ void AudioEnqueue(const void *samples, int count)
((int64_t) count * 90 * 1000) / (AudioSampleRate * AudioChannels *
AudioBytesProSample);
}
+#endif
}
/**
** Video is ready.
+**
+** @param pts video presentation timestamp
*/
-void AudioVideoReady(void)
+void AudioVideoReady(int64_t pts)
{
+#ifdef USE_AUDIORING
+ if (AudioRing[AudioRingWrite].HwSampleRate
+ && AudioRing[AudioRingWrite].HwChannels) {
+ if (pts != (int64_t) INT64_C(0x8000000000000000)
+ && AudioRing[AudioRingWrite].PTS !=
+ (int64_t) INT64_C(0x8000000000000000)) {
+ Debug(3, "audio: a/v %d %s\n",
+ (int)(pts - AudioRing[AudioRingWrite].PTS) / 90,
+ AudioRunning ? "running" : "stopped");
+ }
+ Debug(3, "audio: start %4zdms %s|%s video ready\n",
+ (RingBufferUsedBytes(AudioRing[AudioRingWrite].RingBuffer) * 1000)
+ / (AudioRing[AudioRingWrite].HwSampleRate *
+ AudioRing[AudioRingWrite].HwChannels * AudioBytesProSample),
+ Timestamp2String(pts),
+ Timestamp2String(AudioRing[AudioRingWrite].PTS));
+ if (!AudioRunning) {
+ size_t used;
+
+ used = RingBufferUsedBytes(AudioRing[AudioRingWrite].RingBuffer);
+ // enough video + audio buffered
+ if (AudioStartThreshold < used) {
+ // too much audio buffered, skip it
+ if (AudioStartThreshold * 2 < used) {
+ Debug(3, "audio: start %4zdms skip video ready\n",
+ ((used - AudioStartThreshold * 2) * 1000)
+ / (AudioRing[AudioRingWrite].HwSampleRate *
+ AudioRing[AudioRingWrite].HwChannels *
+ AudioBytesProSample));
+ RingBufferReadAdvance(AudioRing[AudioRingWrite].RingBuffer,
+ used - AudioStartThreshold * 2);
+ }
+ AudioRunning = 1;
+ pthread_cond_signal(&AudioStartCond);
+ }
+ }
+ }
+ AudioVideoIsReady = 1;
+#else
+ (void)pts;
AudioVideoIsReady = 1;
AudioUsedModule->VideoReady();
+#endif
}
/**
@@ -2255,7 +3097,30 @@ void AudioVideoReady(void)
*/
void AudioFlushBuffers(void)
{
+#ifdef USE_AUDIORING
+ int old;
+
+ old = AudioRingWrite;
+ AudioRingWrite = (AudioRingWrite + 1) % AUDIO_RING_MAX;
+ AudioRing[AudioRingWrite].FlushBuffers = 1;
+ AudioRing[AudioRingWrite].UseAc3 = AudioRing[old].UseAc3;
+ AudioRing[AudioRingWrite].HwSampleRate = AudioRing[old].HwSampleRate;
+ AudioRing[AudioRingWrite].HwChannels = AudioRing[old].HwChannels;
+ AudioRing[AudioRingWrite].PTS = INT64_C(0x8000000000000000);
+ RingBufferReadAdvance(AudioRing[AudioRingWrite].RingBuffer,
+ RingBufferUsedBytes(AudioRing[AudioRingWrite].RingBuffer));
+ Debug(3, "audio: reset video ready\n");
+ AudioVideoIsReady = 0;
+
+ atomic_inc(&AudioRingFilled);
+
+ if (!AudioRunning) { // wakeup thread to flush buffers
+ AudioRunning = 1;
+ pthread_cond_signal(&AudioStartCond);
+ }
+#else
AudioUsedModule->FlushBuffers();
+#endif
}
/**
@@ -2271,7 +3136,13 @@ void AudioPoller(void)
*/
int AudioFreeBytes(void)
{
+#ifdef USE_AUDIORING
+ return AudioRing[AudioRingWrite].
+ RingBuffer ? RingBufferFreeBytes(AudioRing[AudioRingWrite].
+ RingBuffer) : INT32_MAX;
+#else
return AudioUsedModule->FreeBytes();
+#endif
}
/**
@@ -2279,7 +3150,13 @@ int AudioFreeBytes(void)
*/
int AudioUsedBytes(void)
{
+#ifdef USE_AUDIORING
+ return AudioRing[AudioRingWrite].
+ RingBuffer ? RingBufferUsedBytes(AudioRing[AudioRingWrite].
+ RingBuffer) : 0;
+#else
return AudioUsedModule->UsedBytes();
+#endif
}
/**
@@ -2289,7 +3166,29 @@ int AudioUsedBytes(void)
*/
int64_t AudioGetDelay(void)
{
+#ifdef USE_AUDIORING
+ int64_t pts;
+
+ if (!AudioRunning) {
+ return 0L; // audio not running
+ }
+ if (!AudioRing[AudioRingRead].HwSampleRate) {
+ return 0L; // audio not setup
+ }
+ if (atomic_read(&AudioRingFilled)) {
+ return 0L; // invalid delay
+ }
+ pts = AudioUsedModule->GetDelay();
+ pts += ((int64_t) RingBufferUsedBytes(AudioRing[AudioRingRead].RingBuffer)
+ * 90 * 1000) / (AudioRing[AudioRingRead].HwSampleRate *
+ AudioRing[AudioRingRead].HwChannels * AudioBytesProSample);
+ Debug(4, "audio/alsa: hw+sw delay %zd %" PRId64 "ms\n",
+ RingBufferUsedBytes(AudioRing[AudioRingRead].RingBuffer), pts / 90);
+
+ return pts;
+#else
return AudioUsedModule->GetDelay();
+#endif
}
/**
@@ -2299,6 +3198,14 @@ int64_t AudioGetDelay(void)
*/
void AudioSetClock(int64_t pts)
{
+#ifdef USE_AUDIORING
+ if (AudioRing[AudioRingWrite].PTS != pts) {
+ Debug(4, "audio: set clock %s -> %s pts\n",
+ Timestamp2String(AudioRing[AudioRingWrite].PTS),
+ Timestamp2String(pts));
+ }
+ AudioRing[AudioRingWrite].PTS = pts;
+#else
#ifdef DEBUG
if (AudioPTS != pts) {
Debug(4, "audio: set clock %s -> %s pts\n", Timestamp2String(AudioPTS),
@@ -2306,6 +3213,7 @@ void AudioSetClock(int64_t pts)
}
#endif
AudioPTS = pts;
+#endif
}
/**
@@ -2315,6 +3223,18 @@ void AudioSetClock(int64_t pts)
*/
int64_t AudioGetClock(void)
{
+#ifdef USE_AUDIORING
+ // (cast) needed for the evil gcc
+ if (AudioRing[AudioRingRead].PTS != (int64_t) INT64_C(0x8000000000000000)) {
+ int64_t delay;
+
+ // delay zero, if no valid time stamp
+ if ((delay = AudioGetDelay())) {
+ return AudioRing[AudioRingRead].PTS - delay;
+ }
+ }
+ return INT64_C(0x8000000000000000);
+#else
// (cast) needed for the evil gcc
if (AudioPTS != (int64_t) INT64_C(0x8000000000000000)) {
int64_t delay;
@@ -2324,16 +3244,34 @@ int64_t AudioGetClock(void)
}
}
return INT64_C(0x8000000000000000);
+#endif
}
/**
-** Set mixer volume (0-100)
+** Set mixer volume (0-1000)
**
-** @param volume volume (0 .. 100)
+** @param volume volume (0 .. 1000)
*/
void AudioSetVolume(int volume)
{
- return AudioUsedModule->SetVolume(volume);
+ AudioVolume = volume;
+#ifdef USE_AUDIORING
+ // reduce loudness for stereo output
+ if (AudioStereoDescent && AudioRing[AudioRingRead].HwChannels == 2
+ && !AudioRing[AudioRingRead].UseAc3) {
+ volume -= AudioStereoDescent;
+ if (volume < 0) {
+ volume = 0;
+ } else if (volume > 1000) {
+ volume = 1000;
+ }
+ }
+#endif
+ AudioAmplifier = volume;
+ printf("volume %d\n", volume);
+ if (!AudioSoftVolume) {
+ AudioUsedModule->SetVolume(volume);
+ }
}
/**
@@ -2346,12 +3284,10 @@ void AudioSetVolume(int volume)
** @retval 0 everything ok
** @retval 1 didn't support frequency/channels combination
** @retval -1 something gone wrong
-**
-** @todo audio changes must be queued and done when the buffer is empty
*/
int AudioSetup(int *freq, int *channels, int use_ac3)
{
- Debug(3, "audio: channels %d frequency %d hz %s\n", *channels, *freq,
+ Debug(3, "audio: setup channels %d frequency %dHz %s\n", *channels, *freq,
use_ac3 ? "ac3" : "pcm");
// invalid parameter
@@ -2362,9 +3298,16 @@ int AudioSetup(int *freq, int *channels, int use_ac3)
}
#ifdef USE_AUDIORING
// FIXME: need to store possible combination and report this
+ if (*freq != 44100 && *freq != 48000) {
+ return -1;
+ }
+ if (*channels < 1 || *channels > 8) {
+ return -1;
+ }
return AudioRingAdd(*freq, *channels, use_ac3);
-#endif
+#else
return AudioUsedModule->Setup(freq, channels, use_ac3);
+#endif
}
/**
@@ -2400,6 +3343,7 @@ void AudioPause(void)
** 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.
+** With streamdev sometimes extra +100ms are needed.
*/
void AudioSetBufferTime(int delay)
{
@@ -2410,6 +3354,69 @@ void AudioSetBufferTime(int delay)
}
/**
+** Enable/disable software volume.
+**
+** @param onoff -1 toggle, true turn on, false turn off
+*/
+void AudioSetSoftvol(int onoff)
+{
+ if (onoff < 0) {
+ AudioSoftVolume ^= 1;
+ } else {
+ AudioSoftVolume = onoff;
+ }
+}
+
+/**
+** Set normalize volume parameters.
+**
+** @param onoff -1 toggle, true turn on, false turn off
+** @param maxfac max. factor of normalize /1000
+*/
+void AudioSetNormalize(int onoff, int maxfac)
+{
+ if (onoff < 0) {
+ AudioNormalize ^= 1;
+ } else {
+ AudioNormalize = onoff;
+ }
+ AudioMaxNormalize = maxfac;
+}
+
+/**
+** Set volume compression parameters.
+**
+** @param onoff -1 toggle, true turn on, false turn off
+** @param maxfac max. factor of compression /1000
+*/
+void AudioSetCompression(int onoff, int maxfac)
+{
+ if (onoff < 0) {
+ AudioCompression ^= 1;
+ } else {
+ AudioCompression = onoff;
+ }
+ AudioMaxCompression = maxfac;
+ if (!AudioCompressionFactor) {
+ AudioCompressionFactor = 1000;
+ }
+ if (AudioCompressionFactor > AudioMaxCompression) {
+ AudioCompressionFactor = AudioMaxCompression;
+ }
+}
+
+/**
+** Set stereo loudness descent.
+**
+** @param delta value (/1000) to reduce stereo volume
+*/
+void AudioSetStereoDescent(int delta)
+{
+ AudioStereoDescent = delta;
+ AudioSetVolume(AudioVolume); // update channel delta
+}
+
+/**
** Set pcm audio device.
**
** @param device name of pcm device (fe. "hw:0,9" or "/dev/dsp")
@@ -2468,11 +3475,14 @@ void AudioSetChannel(const char *channel)
*/
void AudioInit(void)
{
- int freq;
- int chan;
unsigned u;
const char *name;
+#ifndef USE_AUDIORING
+ int freq;
+ int chan;
+#endif
+
name = "noop";
#ifdef USE_OSS
name = "oss";
@@ -2500,19 +3510,20 @@ void AudioInit(void)
found:
#ifdef USE_AUDIORING
AudioRingInit();
-#endif
+ AudioUsedModule->Init();
+#else
AudioUsedModule->Init();
freq = 48000;
chan = 2;
if (AudioSetup(&freq, &chan, 0)) { // set default parameters
Error(_("audio: can't do initial setup\n"));
}
+#endif
#ifdef USE_AUDIO_THREAD
if (AudioUsedModule->Thread) { // supports threads
AudioInitThread();
}
#endif
- AudioPaused = 0;
}
/**
@@ -2521,7 +3532,9 @@ void AudioInit(void)
void AudioExit(void)
{
#ifdef USE_AUDIO_THREAD
- AudioExitThread();
+ if (AudioUsedModule->Thread) { // supports threads
+ AudioExitThread();
+ }
#endif
AudioUsedModule->Exit();
AudioUsedModule = &NoopModule;
@@ -2529,6 +3542,7 @@ void AudioExit(void)
AudioRingExit();
#endif
AudioRunning = 0;
+ AudioPaused = 0;
}
#ifdef AUDIO_TEST
diff --git a/audio.h b/audio.h
index 2393b6f..808d385 100644
--- a/audio.h
+++ b/audio.h
@@ -42,6 +42,10 @@ extern void AudioPlay(void); ///< play audio
extern void AudioPause(void); ///< pause audio
extern void AudioSetBufferTime(int); ///< set audio buffer time
+extern void AudioSetSoftvol(int); ///< enable/disable softvol
+extern void AudioSetNormalize(int, int); ///< set normalize parameters
+extern void AudioSetCompression(int, int); ///< set compression parameters
+extern void AudioSetStereoDescent(int); ///< set stereo loudness descent
extern void AudioSetDevice(const char *); ///< set PCM audio device
extern void AudioSetDeviceAC3(const char *); ///< set pass-through device
diff --git a/codec.c b/codec.c
index 122f8ab..605d6e1 100644
--- a/codec.c
+++ b/codec.c
@@ -949,8 +949,14 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
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);
+ if (1) {
+ static int c;
+
+ if (!(c++ % 10)) {
+ Debug(3, "codec/audio: drift(%6d) %8dus %5d\n",
+ audio_decoder->DriftCorr, drift * 1000 / 90, corr);
+ }
+ }
}
/**
diff --git a/softhddev.c b/softhddev.c
index 75c8cb8..21b075b 100644
--- a/softhddev.c
+++ b/softhddev.c
@@ -61,6 +61,7 @@ static char ConfigVdpauDecoder = 1; ///< use vdpau decoder, if possible
#define ConfigVdpauDecoder 0 ///< no vdpau decoder configured
#endif
+extern int ConfigAudioBufferTime; ///< config size ms of audio buffer
static char ConfigStartSuspended; ///< flag to start in suspend mode
static char ConfigFullscreen; ///< fullscreen modus
static char ConfigStartX11Server; ///< flag start the x11 server
@@ -887,7 +888,8 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
if (NewAudioStream) {
// this clears the audio ringbuffer indirect, open and setup does it
CodecAudioClose(MyAudioDecoder);
- AudioSetBufferTime(0);
+ AudioFlushBuffers();
+ AudioSetBufferTime(ConfigAudioBufferTime);
AudioCodecID = CODEC_ID_NONE;
AudioChannelID = -1;
NewAudioStream = 0;
@@ -972,6 +974,7 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
samplerate = samplerates[p[5] >> 4];
channels = (p[5] & 0x7) + 1;
+ // FIXME: ConfigAudioBufferTime + x
AudioSetBufferTime(400);
AudioSetup(&samplerate, &channels, 0);
if (samplerate != samplerates[p[5] >> 4]) {
@@ -1002,6 +1005,7 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
p += 4;
n -= 4; // skip track header
if (AudioCodecID == CODEC_ID_NONE) {
+ // FIXME: ConfigAudioBufferTime + x
AudioSetBufferTime(400);
}
}
@@ -1100,8 +1104,9 @@ int PlayTsAudio(const uint8_t * data, int size)
if (NewAudioStream) {
// this clears the audio ringbuffer indirect, open and setup does it
CodecAudioClose(MyAudioDecoder);
+ AudioFlushBuffers();
// max time between audio packets 200ms + 24ms hw buffer
- AudioSetBufferTime(264);
+ AudioSetBufferTime(ConfigAudioBufferTime);
AudioCodecID = CODEC_ID_NONE;
AudioChannelID = -1;
NewAudioStream = 0;
@@ -1128,7 +1133,7 @@ int PlayTsAudio(const uint8_t * data, int size)
*/
void SetVolumeDevice(int volume)
{
- AudioSetVolume((volume * 100) / 255);
+ AudioSetVolume((volume * 1000) / 255);
}
//////////////////////////////////////////////////////////////////////////////
@@ -1408,7 +1413,6 @@ int VideoDecode(void)
if (last_codec_id != CODEC_ID_NONE) {
last_codec_id = CODEC_ID_NONE;
CodecVideoClose(MyVideoDecoder);
- VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
goto skip;
}
// FIXME: look if more close are in the queue
@@ -1461,11 +1465,6 @@ int VideoDecode(void)
}
}
- if (ClosingVideoStream) { // closing don't sync
- avpkt->pts = AV_NOPTS_VALUE;
- avpkt->dts = AV_NOPTS_VALUE;
- }
-
if (last_codec_id == CODEC_ID_MPEG2VIDEO) {
FixPacketForFFMpeg(MyVideoDecoder, avpkt);
} else {
@@ -1639,9 +1638,6 @@ int PlayVideo(const uint8_t * data, int size)
}
VideoNextPacket(CODEC_ID_NONE);
VideoCodecID = CODEC_ID_NONE;
- // clear clock until new stream starts
- // FIXME: still reordered frames in queue
- VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
ClosingVideoStream = 1;
NewVideoStream = 0;
}
@@ -1858,6 +1854,8 @@ int SetPlayMode(int play_mode)
if (MyVideoDecoder) { // tell video parser we have new stream
if (VideoCodecID != CODEC_ID_NONE) {
NewVideoStream = 1;
+ // tell hw decoder we are closing stream
+ VideoSetClosing(MyHwDecoder);
#ifdef DEBUG
VideoSwitch = GetMsTicks();
#endif
@@ -1873,11 +1871,9 @@ int SetPlayMode(int play_mode)
break;
case 2: // audio only
Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
- VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
break;
case 3: // audio only, black screen
Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
- VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
break;
case 4: // video only
break;
diff --git a/softhddevice.cpp b/softhddevice.cpp
index 37a8a24..bddc267 100644
--- a/softhddevice.cpp
+++ b/softhddevice.cpp
@@ -35,10 +35,10 @@
#include "softhddevice.h"
extern "C"
{
+#include "audio.h"
#include "video.h"
extern const char *X11DisplayName; ///< x11 display name
- extern void AudioPoller(void);
extern void CodecSetAudioPassthrough(int);
extern void CodecSetAudioDownmix(int);
}
@@ -121,6 +121,7 @@ static int ConfigAudioMaxNormalize; ///< config max normalize factor
static char ConfigAudioCompression; ///< config use volume compression
static int ConfigAudioMaxCompression; ///< config max volume compression
static int ConfigAudioStereoDescent; ///< config reduce stereo loudness
+int ConfigAudioBufferTime; ///< config size ms of audio buffer
static volatile int DoMakePrimary; ///< switch primary device to this
@@ -482,6 +483,8 @@ class cMenuSetupSoft:public cMenuSetupPage
int SuspendX11;
int Video;
+ int VideoFormat;
+ int VideoDisplayFormat;
uint32_t Background;
uint32_t BackgroundAlpha;
int StudioLevels;
@@ -512,6 +515,7 @@ class cMenuSetupSoft:public cMenuSetupPage
int AudioCompression;
int AudioMaxCompression;
int AudioStereoDescent;
+ int AudioBufferTime;
/// @}
private:
inline cOsdItem * CollapsedItem(const char *, int &, const char * = NULL);
@@ -562,6 +566,12 @@ inline cOsdItem *cMenuSetupSoft::CollapsedItem(const char *label, int &flag,
*/
void cMenuSetupSoft::Create(void)
{
+ static const char *const video_display_formats_4_3[] = {
+ "pan&scan", "letterbox", "center cut-out",
+ };
+ static const char *const video_display_formats_16_9[] = {
+ "pan&scan", "pillarbox", "center cut-out",
+ };
static const char *const deinterlace[] = {
"Bob", "Weave/None", "Temporal", "TemporalSpatial", "Software Bob",
"Software Spatial",
@@ -598,26 +608,36 @@ void cMenuSetupSoft::Create(void)
// suspend
//
Add(SeparatorItem(tr("Suspend")));
- Add(new cMenuEditBoolItem(tr("suspend closes video+audio"),
+ Add(new cMenuEditBoolItem(tr("Suspend closes video+audio"),
&SuspendClose, trVDR("no"), trVDR("yes")));
- Add(new cMenuEditBoolItem(tr("suspend stops x11"), &SuspendX11,
+ Add(new cMenuEditBoolItem(tr("Suspend stops x11"), &SuspendX11,
trVDR("no"), trVDR("yes")));
}
//
// video
//
Add(CollapsedItem(tr("Video"), Video));
-
if (Video) {
- Add(new cMenuEditIntItem(tr("video background color (RGB)"),
+ Add(new cMenuEditBoolItem(trVDR("Setup.DVB$Video format"),
+ &VideoFormat, "4:3", "16:9"));
+ if (VideoFormat) {
+ Add(new cMenuEditStraItem(trVDR("Setup.DVB$Video display format"),
+ &VideoDisplayFormat, 3, video_display_formats_16_9));
+ } else {
+ Add(new cMenuEditStraItem(trVDR("Setup.DVB$Video display format"),
+ &VideoDisplayFormat, 3, video_display_formats_4_3));
+ }
+
+ // FIXME: switch config gray/color configuration
+ Add(new cMenuEditIntItem(tr("Video background color (RGB)"),
(int *)&Background, 0, 0x00FFFFFF));
- Add(new cMenuEditIntItem(tr("video background color (Alpha)"),
+ Add(new cMenuEditIntItem(tr("Video background color (Alpha)"),
(int *)&BackgroundAlpha, 0, 0xFF));
Add(new cMenuEditBoolItem(tr("Use studio levels (vdpau only)"),
&StudioLevels, trVDR("no"), trVDR("yes")));
Add(new cMenuEditBoolItem(tr("60hz display mode"), &_60HzMode,
trVDR("no"), trVDR("yes")));
- Add(new cMenuEditBoolItem(tr("soft start a/v sync"), &SoftStartSync,
+ Add(new cMenuEditBoolItem(tr("Soft start a/v sync"), &SoftStartSync,
trVDR("no"), trVDR("yes")));
for (i = 0; i < RESOLUTIONS; ++i) {
@@ -648,11 +668,11 @@ void cMenuSetupSoft::Create(void)
// auto-crop
//
Add(SeparatorItem(tr("Auto-crop")));
- Add(new cMenuEditIntItem(tr("autocrop interval (frames)"),
+ Add(new cMenuEditIntItem(tr("Autocrop interval (frames)"),
&AutoCropInterval, 0, 200, tr("off")));
- Add(new cMenuEditIntItem(tr("autocrop delay (n * interval)"),
+ Add(new cMenuEditIntItem(tr("Autocrop delay (n * interval)"),
&AutoCropDelay, 0, 200));
- Add(new cMenuEditIntItem(tr("autocrop tolerance (pixel)"),
+ Add(new cMenuEditIntItem(tr("Autocrop tolerance (pixel)"),
&AutoCropTolerance, 0, 32));
}
//
@@ -667,6 +687,20 @@ void cMenuSetupSoft::Create(void)
2, passthrough));
Add(new cMenuEditBoolItem(tr("Enable AC-3 downmix"), &AudioDownmix,
trVDR("no"), trVDR("yes")));
+ Add(new cMenuEditBoolItem(tr("Volume control"), &AudioSoftvol,
+ tr("Hardware"), tr("Software")));
+ Add(new cMenuEditBoolItem(tr("Enable normalize volume"),
+ &AudioMaxNormalize, trVDR("no"), trVDR("yes")));
+ Add(new cMenuEditIntItem(tr(" Max normalize factor (/1000)"),
+ &AudioMaxNormalize, 0, 5000));
+ Add(new cMenuEditBoolItem(tr("Enable volume compression"),
+ &AudioCompression, trVDR("no"), trVDR("yes")));
+ Add(new cMenuEditIntItem(tr(" Max compression factor (/1000)"),
+ &AudioMaxCompression, 0, 10000));
+ Add(new cMenuEditIntItem(tr("Reduce stereo volume (/1000)"),
+ &AudioStereoDescent, 0, 1000));
+ Add(new cMenuEditIntItem(tr("Audio buffer size (ms)"),
+ &AudioBufferTime, 0, 1000));
}
SetCurrent(Get(current)); // restore selected menu entry
@@ -682,19 +716,22 @@ eOSState cMenuSetupSoft::ProcessKey(eKeys key)
int old_general;
int old_video;
int old_audio;
+ int old_video_format;
int old_resolution_shown[RESOLUTIONS];
int i;
old_general = General;
old_video = Video;
old_audio = Audio;
+ old_video_format = VideoFormat;
memcpy(old_resolution_shown, ResolutionShown, sizeof(ResolutionShown));
state = cMenuSetupPage::ProcessKey(key);
if (key != kNone) {
// update menu only, if something on the structure has changed
// this needed because VDR menus are evil slow
- if (old_general != General || old_video != Video || old_audio != Audio) {
+ if (old_general != General || old_video != Video
+ || old_audio != Audio || old_video_format != VideoFormat) {
Create(); // update menu
} else {
for (i = 0; i < RESOLUTIONS; ++i) {
@@ -734,6 +771,8 @@ cMenuSetupSoft::cMenuSetupSoft(void)
// video
//
Video = 0;
+ VideoFormat = Setup.VideoFormat;
+ VideoDisplayFormat = Setup.VideoDisplayFormat;
// no unsigned int menu item supported, split background color/alpha
Background = ConfigVideoBackground >> 8;
BackgroundAlpha = ConfigVideoBackground & 0xFF;
@@ -773,6 +812,7 @@ cMenuSetupSoft::cMenuSetupSoft(void)
AudioCompression = ConfigAudioCompression;
AudioMaxCompression = ConfigAudioMaxCompression;
AudioStereoDescent = ConfigAudioStereoDescent;
+ AudioBufferTime = ConfigAudioBufferTime;
Create();
}
@@ -789,6 +829,20 @@ void cMenuSetupSoft::Store(void)
HideMainMenuEntry);
SetupStore("Suspend.Close", ConfigSuspendClose = SuspendClose);
SetupStore("Suspend.X11", ConfigSuspendX11 = SuspendX11);
+ // FIXME: this is also in VDR-DVB setup
+ if (Setup.VideoFormat != VideoFormat) {
+ Setup.VideoFormat = VideoFormat;
+ cDevice::PrimaryDevice()->SetVideoFormat(Setup.VideoFormat);
+ printf("video-format\n");
+ }
+ SetupStore("VideoFormat", Setup.VideoFormat);
+ if (Setup.VideoDisplayFormat != VideoDisplayFormat) {
+ Setup.VideoDisplayFormat = VideoDisplayFormat;
+ cDevice::PrimaryDevice()->SetVideoDisplayFormat(
+ eVideoDisplayFormat(Setup.VideoDisplayFormat));
+ printf("video-display-format\n");
+ }
+ SetupStore("VideoDisplayFormat", Setup.VideoDisplayFormat);
ConfigVideoBackground = Background << 8 | (BackgroundAlpha & 0xFF);
SetupStore("Background", ConfigVideoBackground);
@@ -838,7 +892,7 @@ void cMenuSetupSoft::Store(void)
AutoCropTolerance);
VideoSetAutoCrop(ConfigAutoCropInterval, ConfigAutoCropDelay,
ConfigAutoCropTolerance);
- ConfigAutoCropEnabled = ConfigAutoCropInterval;
+ ConfigAutoCropEnabled = ConfigAutoCropInterval != 0;
SetupStore("AudioDelay", ConfigVideoAudioDelay = AudioDelay);
VideoSetAudioDelay(ConfigVideoAudioDelay);
@@ -846,7 +900,17 @@ void cMenuSetupSoft::Store(void)
CodecSetAudioPassthrough(ConfigAudioPassthrough);
SetupStore("AudioDownmix", ConfigAudioDownmix = AudioDownmix);
CodecSetAudioDownmix(ConfigAudioDownmix);
- // FIXME: new audio
+ SetupStore("AudioSoftvol", ConfigAudioSoftvol = AudioSoftvol );
+ AudioSetSoftvol(ConfigAudioSoftvol);
+ SetupStore("AudioNormalize", ConfigAudioNormalize = AudioNormalize );
+ SetupStore("AudioMaxNormalize", ConfigAudioMaxNormalize = AudioMaxNormalize );
+ AudioSetNormalize(ConfigAudioNormalize, ConfigAudioMaxNormalize);
+ SetupStore("AudioCompression", ConfigAudioCompression = AudioCompression );
+ SetupStore("AudioMaxCompression", ConfigAudioMaxCompression = AudioMaxCompression );
+ AudioSetCompression(ConfigAudioCompression, ConfigAudioMaxCompression);
+ SetupStore("AudioStereoDescent", ConfigAudioStereoDescent = AudioStereoDescent );
+ AudioSetStereoDescent(ConfigAudioStereoDescent);
+ SetupStore("AudioBufferTime", ConfigAudioBufferTime = AudioBufferTime );
}
//////////////////////////////////////////////////////////////////////////////
@@ -1039,7 +1103,7 @@ static void HandleHotkey(int code)
Skins.QueueMessage(mtInfo, tr("auto-crop enabled"));
break;
case 25: // toggle auto-crop
- ConfigAutoCropEnabled = !ConfigAutoCropEnabled;
+ ConfigAutoCropEnabled ^= 1;
// no interval configured, use some default
if (!ConfigAutoCropInterval) {
ConfigAutoCropInterval = 50;
@@ -1283,6 +1347,7 @@ bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode)
return true;
case pmExtern_THIS_SHOULD_BE_AVOIDED:
dsyslog("[softhddev] play mode external\n");
+ // FIXME: what if already suspended?
Suspend(1, 1, 0);
SuspendMode = SUSPEND_EXTERNAL;
return true;
@@ -1916,7 +1981,7 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
if (!strcasecmp(name, "AutoCrop.Interval")) {
VideoSetAutoCrop(ConfigAutoCropInterval =
atoi(value), ConfigAutoCropDelay, ConfigAutoCropTolerance);
- ConfigAutoCropEnabled = ConfigAutoCropInterval;
+ ConfigAutoCropEnabled = ConfigAutoCropInterval != 0;
return true;
}
if (!strcasecmp(name, "AutoCrop.Delay")) {
@@ -1942,7 +2007,39 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
CodecSetAudioDownmix(ConfigAudioDownmix = atoi(value));
return true;
}
- // FIXME: new audio
+ if (!strcasecmp(name, "AudioSoftvol")) {
+ AudioSetSoftvol(ConfigAudioSoftvol = atoi(value));
+ return true;
+ }
+ if (!strcasecmp(name, "AudioNormalize")) {
+ ConfigAudioNormalize = atoi(value);
+ AudioSetNormalize(ConfigAudioNormalize, ConfigAudioMaxNormalize);
+ return true;
+ }
+ if (!strcasecmp(name, "AudioMaxNormalize")) {
+ ConfigAudioMaxNormalize = atoi(value);
+ AudioSetNormalize(ConfigAudioNormalize, ConfigAudioMaxNormalize);
+ return true;
+ }
+ if (!strcasecmp(name, "AudioCompression")) {
+ ConfigAudioCompression = atoi(value);
+ AudioSetCompression(ConfigAudioCompression, ConfigAudioMaxCompression);
+ return true;
+ }
+ if (!strcasecmp(name, "AudioMaxCompression")) {
+ ConfigAudioMaxCompression = atoi(value);
+ AudioSetCompression(ConfigAudioCompression, ConfigAudioMaxCompression);
+ return true;
+ }
+ if (!strcasecmp(name, "AudioStereoDescent")) {
+ ConfigAudioStereoDescent = atoi(value);
+ AudioSetStereoDescent(ConfigAudioStereoDescent);
+ return true;
+ }
+ if (!strcasecmp(name, "AudioBufferTime")) {
+ ConfigAudioBufferTime = atoi(value);
+ return true;
+ }
return false;
}
diff --git a/video.c b/video.c
index d4ace49..76be58e 100644
--- a/video.c
+++ b/video.c
@@ -356,7 +356,7 @@ static xcb_atom_t NetWmStateFullscreen; ///< fullscreen wm-state message atom
#ifdef DEBUG
extern uint32_t VideoSwitch; ///< ticks for channel switch
#endif
-extern void AudioVideoReady(void); ///< tell audio video is ready
+extern void AudioVideoReady(int64_t); ///< tell audio video is ready
#ifdef USE_VIDEO_THREAD
@@ -427,6 +427,8 @@ static void VideoSetPts(int64_t * pts_p, int interlaced, const AVFrame * frame)
}
return;
}
+ } else { // first new clock value
+ AudioVideoReady(pts);
}
if (*pts_p != pts) {
Debug(4,
@@ -1334,6 +1336,7 @@ struct _vaapi_decoder_
int TrickSpeed; ///< current trick speed
int TrickCounter; ///< current trick speed counter
struct timespec FrameTime; ///< time of last display
+ int Closing; ///< flag about closing current stream
int64_t PTS; ///< video PTS clock
int SyncCounter; ///< counter to sync frames
@@ -1856,6 +1859,7 @@ static void VaapiCleanup(VaapiDecoder * decoder)
decoder->FrameCounter = 0;
decoder->FramesDisplayed = 0;
+ decoder->Closing = 0;
decoder->PTS = AV_NOPTS_VALUE;
VideoDeltaPTS = 0;
}
@@ -4504,6 +4508,9 @@ static void VaapiSyncDecoder(VaapiDecoder * decoder)
_("video: decoder buffer empty, "
"duping frame (%d/%d) %d v-buf\n"), decoder->FramesDuped,
decoder->FrameCounter, VideoGetBuffers());
+ if (decoder->Closing == -1) {
+ atomic_set(&decoder->SurfacesFilled, 0);
+ }
}
goto out;
}
@@ -4600,7 +4607,9 @@ static void VaapiSyncRenderFrame(VaapiDecoder * decoder,
VaapiSyncDisplayFrame();
}
- VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ if (!decoder->Closing) {
+ VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ }
VaapiRenderFrame(decoder, video_ctx, frame);
#ifdef USE_AUTOCROP
VaapiCheckAutoCrop(decoder);
@@ -4661,6 +4670,10 @@ static void VaapiDisplayHandlerThread(void)
if (err) {
// FIXME: sleep on wakeup
usleep(5 * 1000); // nothing buffered
+ if (err == -1 && decoder->Closing > 0) {
+ Debug(3, "video/vaapi: closing eof\n");
+ decoder->Closing = -1;
+ }
}
clock_gettime(CLOCK_REALTIME, &nowtime);
@@ -5002,6 +5015,7 @@ typedef struct _vdpau_decoder_
int TrickSpeed; ///< current trick speed
int TrickCounter; ///< current trick speed counter
struct timespec FrameTime; ///< time of last display
+ int Closing; ///< flag about closing current stream
int64_t PTS; ///< video PTS clock
int SyncCounter; ///< counter to sync frames
@@ -5721,6 +5735,7 @@ static void VdpauCleanup(VdpauDecoder * decoder)
decoder->FrameCounter = 0;
decoder->FramesDisplayed = 0;
+ decoder->Closing = 0;
decoder->PTS = AV_NOPTS_VALUE;
VideoDeltaPTS = 0;
}
@@ -7711,6 +7726,9 @@ static void VdpauSyncDecoder(VdpauDecoder * decoder)
_("video: decoder buffer empty, "
"duping frame (%d/%d) %d v-buf\n"), decoder->FramesDuped,
decoder->FrameCounter, VideoGetBuffers());
+ if (decoder->Closing == -1) {
+ atomic_set(&decoder->SurfacesFilled, 0);
+ }
}
goto out;
}
@@ -7782,7 +7800,9 @@ static void VdpauSyncRenderFrame(VdpauDecoder * decoder,
#endif
if (VdpauPreemption) { // display preempted
- VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ if (!decoder->Closing) {
+ VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ }
return;
}
// if video output buffer is full, wait and display surface.
@@ -7821,7 +7841,9 @@ static void VdpauSyncRenderFrame(VdpauDecoder * decoder,
VdpauSyncDisplayFrame();
}
- VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ if (!decoder->Closing) {
+ VideoSetPts(&decoder->PTS, decoder->Interlaced, frame);
+ }
VdpauRenderFrame(decoder, video_ctx, frame);
}
@@ -7944,6 +7966,10 @@ static void VdpauDisplayHandlerThread(void)
if (err) {
// FIXME: sleep on wakeup
usleep(5 * 1000); // nothing buffered
+ if (err == -1 && decoder->Closing > 0) {
+ Debug(3, "video/vdpau: closing eof\n");
+ decoder->Closing = -1;
+ }
}
clock_gettime(CLOCK_REALTIME, &nowtime);
@@ -9001,7 +9027,7 @@ enum PixelFormat Video_get_format(VideoHwDecoder * hw_decoder,
Debug(3, "video: ready %s %dms/frame\n",
Timestamp2String(VideoGetClock(hw_decoder)), ms_delay);
- AudioVideoReady();
+ AudioVideoReady(VideoGetClock(hw_decoder));
return VideoUsedModule->get_format(hw_decoder, video_ctx, fmt);
}
@@ -9127,6 +9153,29 @@ int64_t VideoGetClock(const VideoHwDecoder * hw_decoder)
}
///
+/// Set closing stream flag.
+///
+/// @param hw_decoder video hardware decoder
+///
+void VideoSetClosing(VideoHwDecoder * hw_decoder)
+{
+ Debug(3, "video: set closing\n");
+ // FIXME: test to check if working, than make module function
+#ifdef USE_VDPAU
+ if (VideoUsedModule == &VdpauModule) {
+ hw_decoder->Vdpau.Closing = 1;
+ }
+#endif
+#ifdef USE_VAPI
+ if (VideoUsedModule == &VaapiModule) {
+ hw_decoder->Vaapi.Closing = 1;
+ }
+#endif
+ // clear clock to avoid further sync
+ VideoSetClock(hw_decoder, AV_NOPTS_VALUE);
+}
+
+///
/// Set trick play speed.
///
/// @param hw_decoder video hardware decoder
diff --git a/video.h b/video.h
index 397f5df..20e016d 100644
--- a/video.h
+++ b/video.h
@@ -153,6 +153,9 @@ extern void VideoSetClock(VideoHwDecoder *, int64_t);
/// Get video clock.
extern int64_t VideoGetClock(const VideoHwDecoder *);
+ /// Set closing flag.
+extern void VideoSetClosing(VideoHwDecoder *);
+
/// Set trick play speed.
extern void VideoSetTrickSpeed(VideoHwDecoder *, int);