mirror of
https://projects.vdr-developer.org/git/vdr-plugin-softhddevice.git
synced 2023-10-10 19:16:51 +02:00
Improved pass-through (PCM+EAC3) support.
This commit is contained in:
parent
145d65ff01
commit
2cd667fb44
@ -1,6 +1,7 @@
|
||||
User johns
|
||||
Date:
|
||||
|
||||
Improved pass-through (PCM+EAC3) support.
|
||||
Support VDR 1.7.36 new build system.
|
||||
Improves VDPAU display preemption handling.
|
||||
Add modifiers to X11 remote key names (from Sibbi).
|
||||
|
19
README.txt
19
README.txt
@ -1,6 +1,6 @@
|
||||
@file README.txt @brief A software HD output device for VDR
|
||||
|
||||
Copyright (c) 2011, 2012 by Johns. All Rights Reserved.
|
||||
Copyright (c) 2011 - 2013 by Johns. All Rights Reserved.
|
||||
|
||||
Contributor(s):
|
||||
|
||||
@ -89,8 +89,8 @@ Setup: environment
|
||||
only if alsa is configured
|
||||
ALSA_DEVICE=default
|
||||
alsa PCM device name
|
||||
ALSA_AC3_DEVICE=
|
||||
alsa AC3/pass-though device name
|
||||
ALSA_PASSTHROUGH_DEVICE=
|
||||
alsa pass-though (AC3,EAC3,DTS,...) device name
|
||||
ALSA_MIXER=default
|
||||
alsa control device name
|
||||
ALSA_MIXER_CHANNEL=PCM
|
||||
@ -99,8 +99,8 @@ Setup: environment
|
||||
only if oss is configured
|
||||
OSS_AUDIODEV=/dev/dsp
|
||||
oss dsp device name
|
||||
OSS_AC3_AUDIODEV=
|
||||
oss AC3/pass-though device name
|
||||
OSS_PASSTHROUGHDEV=
|
||||
oss pass-though (AC3,EAC3,DTS,...) device name
|
||||
OSS_MIXERDEV=/dev/mixer
|
||||
oss mixer device name
|
||||
OSS_MIXER_CHANNEL=pcm
|
||||
@ -156,13 +156,16 @@ Setup: /etc/vdr/setup.conf
|
||||
delay audio or delay video
|
||||
|
||||
softhddevice.AudioPassthrough = 0
|
||||
0 = none, 1 = AC-3
|
||||
0 = none, 1 = PCM, 2 = MPA, 4 = AC-3, 8 = EAC-3
|
||||
|
||||
for AC-3 the pass-through device is used.
|
||||
for PCM/AC-3/EAC-3 the pass-through device is used and the audio
|
||||
stream is passed undecoded to the output device.
|
||||
z.b. 12 = AC-3+EAC-3, 13 = PCM+AC-3+EAC-3
|
||||
note: MPA/DTS/TrueHD/... aren't supported yet
|
||||
|
||||
softhddevice.AudioDownmix = 0
|
||||
0 = none, 1 = downmix
|
||||
Use ffmpeg/libav downmix AC-3 to stereo.
|
||||
Use ffmpeg/libav downmix of AC-3/EAC-3 audio to stereo.
|
||||
|
||||
softhddevice.AudioSoftvol = 0
|
||||
0 = off, use hardware volume control
|
||||
|
101
audio.c
101
audio.c
@ -129,7 +129,7 @@ static const char *AudioModuleName; ///< which audio module to use
|
||||
/// Selected audio module.
|
||||
static const AudioModule *AudioUsedModule = &NoopModule;
|
||||
static const char *AudioPCMDevice; ///< PCM device name
|
||||
static const char *AudioAC3Device; ///< AC3 device name
|
||||
static const char *AudioPassthroughDevice; ///< Passthrough device name
|
||||
static const char *AudioMixerDevice; ///< mixer device name
|
||||
static const char *AudioMixerChannel; ///< mixer channel name
|
||||
static char AudioDoingInit; ///> flag in init, reduce error
|
||||
@ -620,7 +620,7 @@ static void AudioResample(const int16_t * in, int in_chan, int frames,
|
||||
typedef struct _audio_ring_ring_
|
||||
{
|
||||
char FlushBuffers; ///< flag: flush buffers
|
||||
char UseAc3; ///< flag: use ac3 pass-through
|
||||
char Passthrough; ///< flag: use pass-through (AC3, ...)
|
||||
int16_t PacketSize; ///< packet size
|
||||
unsigned HwSampleRate; ///< hardware sample rate in Hz
|
||||
unsigned HwChannels; ///< hardware number of channels
|
||||
@ -642,14 +642,14 @@ static unsigned AudioStartThreshold; ///< start play, if filled
|
||||
**
|
||||
** @param sample_rate sample-rate frequency
|
||||
** @param channels number of channels
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use /pass-through (AC3, ...) device
|
||||
**
|
||||
** @retval -1 error
|
||||
** @retval 0 okay
|
||||
**
|
||||
** @note this function shouldn't fail. Checks are done during AudoInit.
|
||||
*/
|
||||
static int AudioRingAdd(unsigned sample_rate, int channels, int use_ac3)
|
||||
static int AudioRingAdd(unsigned sample_rate, int channels, int passthrough)
|
||||
{
|
||||
unsigned u;
|
||||
|
||||
@ -680,7 +680,7 @@ static int AudioRingAdd(unsigned sample_rate, int channels, int use_ac3)
|
||||
|
||||
// FIXME: don't flush buffers here
|
||||
AudioRing[AudioRingWrite].FlushBuffers = 1;
|
||||
AudioRing[AudioRingWrite].UseAc3 = use_ac3;
|
||||
AudioRing[AudioRingWrite].Passthrough = passthrough;
|
||||
AudioRing[AudioRingWrite].PacketSize = 0;
|
||||
AudioRing[AudioRingWrite].InSampleRate = sample_rate;
|
||||
AudioRing[AudioRingWrite].InChannels = channels;
|
||||
@ -836,8 +836,9 @@ static int AlsaPlayRingbuffer(void)
|
||||
if (!avail) { // full or buffer empty
|
||||
break;
|
||||
}
|
||||
// muting ac3, can produce disturbance
|
||||
if (AudioMute || (AudioSoftVolume && !AudioRing[AudioRingRead].UseAc3)) {
|
||||
// muting pass-through ac3, can produce disturbance
|
||||
if (AudioMute || (AudioSoftVolume
|
||||
&& !AudioRing[AudioRingRead].Passthrough)) {
|
||||
// FIXME: quick&dirty cast
|
||||
AudioSoftAmplifier((int16_t *) p, avail);
|
||||
// FIXME: if not all are written, we double amplify them
|
||||
@ -984,23 +985,23 @@ static int AlsaThread(void)
|
||||
/**
|
||||
** Open alsa pcm device.
|
||||
**
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
*/
|
||||
static snd_pcm_t *AlsaOpenPCM(int use_ac3)
|
||||
static snd_pcm_t *AlsaOpenPCM(int passthrough)
|
||||
{
|
||||
const char *device;
|
||||
snd_pcm_t *handle;
|
||||
int err;
|
||||
|
||||
// &&|| hell
|
||||
if (!(use_ac3 && ((device = AudioAC3Device)
|
||||
|| (device = getenv("ALSA_AC3_DEVICE"))))
|
||||
if (!(passthrough && ((device = AudioPassthroughDevice)
|
||||
|| (device = getenv("ALSA_PASSTHROUGH_DEVICE"))))
|
||||
&& !(device = AudioPCMDevice) && !(device = getenv("ALSA_DEVICE"))) {
|
||||
device = "default";
|
||||
}
|
||||
if (!AudioDoingInit) { // reduce blabla during init
|
||||
Info(_("audio/alsa: using %sdevice '%s'\n"), use_ac3 ? "ac3 " : "",
|
||||
device);
|
||||
Info(_("audio/alsa: using %sdevice '%s'\n"),
|
||||
passthrough ? "pass-through " : "", device);
|
||||
}
|
||||
// open none blocking; if device is already used, we don't want wait
|
||||
if ((err =
|
||||
@ -1167,7 +1168,7 @@ static int64_t AlsaGetDelay(void)
|
||||
**
|
||||
** @param freq sample frequency
|
||||
** @param channels number of channels
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
**
|
||||
** @retval 0 everything ok
|
||||
** @retval 1 didn't support frequency/channels combination
|
||||
@ -1175,7 +1176,7 @@ static int64_t AlsaGetDelay(void)
|
||||
**
|
||||
** @todo FIXME: remove pointer for freq + channels
|
||||
*/
|
||||
static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
static int AlsaSetup(int *freq, int *channels, int passthrough)
|
||||
{
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_uframes_t period_size;
|
||||
@ -1183,7 +1184,7 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
int delay;
|
||||
|
||||
if (!AlsaPCMHandle) { // alsa not running yet
|
||||
// FIXME: if open fails for ac3, we never recover
|
||||
// FIXME: if open fails for fe. pass-through, we never recover
|
||||
return -1;
|
||||
}
|
||||
if (1) { // close+open to fix HDMI no sound bug
|
||||
@ -1193,7 +1194,7 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
// FIXME: need lock
|
||||
AlsaPCMHandle = NULL; // other threads should check handle
|
||||
snd_pcm_close(handle);
|
||||
if (!(handle = AlsaOpenPCM(use_ac3))) {
|
||||
if (!(handle = AlsaOpenPCM(passthrough))) {
|
||||
return -1;
|
||||
}
|
||||
AlsaPCMHandle = handle;
|
||||
@ -1453,7 +1454,7 @@ static int OssPlayRingbuffer(void)
|
||||
break; // bi.bytes could become negative!
|
||||
}
|
||||
|
||||
if (AudioSoftVolume && !AudioRing[AudioRingRead].UseAc3) {
|
||||
if (AudioSoftVolume && !AudioRing[AudioRingRead].Passthrough) {
|
||||
// FIXME: quick&dirty cast
|
||||
AudioSoftAmplifier((int16_t *) p, bi.bytes);
|
||||
// FIXME: if not all are written, we double amplify them
|
||||
@ -1560,22 +1561,22 @@ static int OssThread(void)
|
||||
/**
|
||||
** Open OSS pcm device.
|
||||
**
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
*/
|
||||
static int OssOpenPCM(int use_ac3)
|
||||
static int OssOpenPCM(int passthrough)
|
||||
{
|
||||
const char *device;
|
||||
int fildes;
|
||||
|
||||
// &&|| hell
|
||||
if (!(use_ac3 && ((device = AudioAC3Device)
|
||||
|| (device = getenv("OSS_AC3_AUDIODEV"))))
|
||||
if (!(passthrough && ((device = AudioPassthroughDevice)
|
||||
|| (device = getenv("OSS_PASSTHROUGHDEV"))))
|
||||
&& !(device = AudioPCMDevice) && !(device = getenv("OSS_AUDIODEV"))) {
|
||||
device = "/dev/dsp";
|
||||
}
|
||||
if (!AudioDoingInit) {
|
||||
Info(_("audio/oss: using %sdevice '%s'\n"), use_ac3 ? "ac3 " : "",
|
||||
device);
|
||||
Info(_("audio/oss: using %sdevice '%s'\n"),
|
||||
passthrough ? "pass-through " : "", device);
|
||||
}
|
||||
|
||||
if ((fildes = open(device, O_WRONLY)) < 0) {
|
||||
@ -1724,13 +1725,13 @@ static int64_t OssGetDelay(void)
|
||||
**
|
||||
** @param sample_rate sample rate/frequency
|
||||
** @param channels number of channels
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
**
|
||||
** @retval 0 everything ok
|
||||
** @retval 1 didn't support frequency/channels combination
|
||||
** @retval -1 something gone wrong
|
||||
*/
|
||||
static int OssSetup(int *sample_rate, int *channels, int use_ac3)
|
||||
static int OssSetup(int *sample_rate, int *channels, int passthrough)
|
||||
{
|
||||
int ret;
|
||||
int tmp;
|
||||
@ -1738,7 +1739,7 @@ static int OssSetup(int *sample_rate, int *channels, int use_ac3)
|
||||
audio_buf_info bi;
|
||||
|
||||
if (OssPcmFildes == -1) { // OSS not ready
|
||||
// FIXME: if open fails for ac3, we never recover
|
||||
// FIXME: if open fails for fe. pass-through, we never recover
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1748,7 +1749,7 @@ static int OssSetup(int *sample_rate, int *channels, int use_ac3)
|
||||
fildes = OssPcmFildes;
|
||||
OssPcmFildes = -1;
|
||||
close(fildes);
|
||||
if (!(fildes = OssOpenPCM(use_ac3))) {
|
||||
if (!(fildes = OssOpenPCM(passthrough))) {
|
||||
return -1;
|
||||
}
|
||||
OssPcmFildes = fildes;
|
||||
@ -1931,11 +1932,12 @@ static void NoopSetVolume( __attribute__ ((unused))
|
||||
**
|
||||
** @param freq sample frequency
|
||||
** @param channels number of channels
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
*/
|
||||
static int NoopSetup( __attribute__ ((unused))
|
||||
int *channels, __attribute__ ((unused))
|
||||
int *freq, __attribute__ ((unused))
|
||||
int use_ac3)
|
||||
int passthrough)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@ -1973,17 +1975,17 @@ static const AudioModule NoopModule = {
|
||||
*/
|
||||
static int AudioNextRing(void)
|
||||
{
|
||||
int use_ac3;
|
||||
int passthrough;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
size_t used;
|
||||
|
||||
// update audio format
|
||||
// not always needed, but check if needed is too complex
|
||||
use_ac3 = AudioRing[AudioRingRead].UseAc3;
|
||||
passthrough = AudioRing[AudioRingRead].Passthrough;
|
||||
sample_rate = AudioRing[AudioRingRead].HwSampleRate;
|
||||
channels = AudioRing[AudioRingRead].HwChannels;
|
||||
if (AudioUsedModule->Setup(&sample_rate, &channels, use_ac3)) {
|
||||
if (AudioUsedModule->Setup(&sample_rate, &channels, passthrough)) {
|
||||
Error(_("audio: can't set channels %d sample-rate %dHz\n"), channels,
|
||||
sample_rate);
|
||||
// FIXME: handle error
|
||||
@ -2068,10 +2070,10 @@ static void *AudioPlayHandlerThread(void *dummy)
|
||||
err = AudioUsedModule->Thread();
|
||||
// underrun, check if new ring buffer is available
|
||||
if (!err) {
|
||||
int use_ac3;
|
||||
int passthrough;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
int old_use_ac3;
|
||||
int old_passthrough;
|
||||
int old_sample_rate;
|
||||
int old_channels;
|
||||
|
||||
@ -2081,20 +2083,21 @@ static void *AudioPlayHandlerThread(void *dummy)
|
||||
}
|
||||
|
||||
Debug(3, "audio: next ring buffer\n");
|
||||
old_use_ac3 = AudioRing[AudioRingRead].UseAc3;
|
||||
old_passthrough = AudioRing[AudioRingRead].Passthrough;
|
||||
old_sample_rate = AudioRing[AudioRingRead].HwSampleRate;
|
||||
old_channels = AudioRing[AudioRingRead].HwChannels;
|
||||
|
||||
atomic_dec(&AudioRingFilled);
|
||||
AudioRingRead = (AudioRingRead + 1) % AUDIO_RING_MAX;
|
||||
|
||||
use_ac3 = AudioRing[AudioRingRead].UseAc3;
|
||||
passthrough = AudioRing[AudioRingRead].Passthrough;
|
||||
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");
|
||||
channels, sample_rate, passthrough ? "pass-through" : "");
|
||||
// audio config changed?
|
||||
if (old_use_ac3 != use_ac3 || old_sample_rate != sample_rate
|
||||
if (old_passthrough != passthrough
|
||||
|| old_sample_rate != sample_rate
|
||||
|| old_channels != channels) {
|
||||
// FIXME: wait for buffer drain
|
||||
if (AudioNextRing()) {
|
||||
@ -2196,7 +2199,7 @@ void AudioEnqueue(const void *samples, int count)
|
||||
}
|
||||
// audio sample modification allowed and needed?
|
||||
buffer = (void *)samples;
|
||||
if (!AudioRing[AudioRingWrite].UseAc3 && (AudioCompression
|
||||
if (!AudioRing[AudioRingWrite].Passthrough && (AudioCompression
|
||||
|| AudioNormalize
|
||||
|| AudioRing[AudioRingWrite].InChannels !=
|
||||
AudioRing[AudioRingWrite].HwChannels)) {
|
||||
@ -2416,7 +2419,7 @@ void AudioFlushBuffers(void)
|
||||
old = AudioRingWrite;
|
||||
AudioRingWrite = (AudioRingWrite + 1) % AUDIO_RING_MAX;
|
||||
AudioRing[AudioRingWrite].FlushBuffers = 1;
|
||||
AudioRing[AudioRingWrite].UseAc3 = AudioRing[old].UseAc3;
|
||||
AudioRing[AudioRingWrite].Passthrough = AudioRing[old].Passthrough;
|
||||
AudioRing[AudioRingWrite].HwSampleRate = AudioRing[old].HwSampleRate;
|
||||
AudioRing[AudioRingWrite].HwChannels = AudioRing[old].HwChannels;
|
||||
AudioRing[AudioRingWrite].InSampleRate = AudioRing[old].InSampleRate;
|
||||
@ -2528,7 +2531,7 @@ int64_t AudioGetClock(void)
|
||||
|
||||
// delay zero, if no valid time stamp
|
||||
if ((delay = AudioGetDelay())) {
|
||||
if (AudioRing[AudioRingRead].UseAc3) {
|
||||
if (AudioRing[AudioRingRead].Passthrough) {
|
||||
return AudioRing[AudioRingRead].PTS + 0 * 90 - delay;
|
||||
}
|
||||
return AudioRing[AudioRingRead].PTS + 0 * 90 - delay;
|
||||
@ -2548,7 +2551,7 @@ void AudioSetVolume(int volume)
|
||||
AudioMute = !volume;
|
||||
// reduce loudness for stereo output
|
||||
if (AudioStereoDescent && AudioRing[AudioRingRead].InChannels == 2
|
||||
&& !AudioRing[AudioRingRead].UseAc3) {
|
||||
&& !AudioRing[AudioRingRead].Passthrough) {
|
||||
volume -= AudioStereoDescent;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
@ -2567,7 +2570,7 @@ void AudioSetVolume(int volume)
|
||||
**
|
||||
** @param freq sample frequency
|
||||
** @param channels number of channels
|
||||
** @param use_ac3 use ac3/pass-through device
|
||||
** @param passthrough use pass-through (AC3, ...) device
|
||||
**
|
||||
** @retval 0 everything ok
|
||||
** @retval 1 didn't support frequency/channels combination
|
||||
@ -2575,10 +2578,10 @@ void AudioSetVolume(int volume)
|
||||
**
|
||||
** @todo add support to report best fitting format.
|
||||
*/
|
||||
int AudioSetup(int *freq, int *channels, int use_ac3)
|
||||
int AudioSetup(int *freq, int *channels, int passthrough)
|
||||
{
|
||||
Debug(3, "audio: setup channels %d frequency %dHz %s\n", *channels, *freq,
|
||||
use_ac3 ? "ac3" : "pcm");
|
||||
passthrough ? "pass-through" : "");
|
||||
|
||||
// invalid parameter
|
||||
if (!freq || !channels || !*freq || !*channels) {
|
||||
@ -2586,7 +2589,7 @@ int AudioSetup(int *freq, int *channels, int use_ac3)
|
||||
// FIXME: set flag invalid setup
|
||||
return -1;
|
||||
}
|
||||
return AudioRingAdd(*freq, *channels, use_ac3);
|
||||
return AudioRingAdd(*freq, *channels, passthrough);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2722,7 +2725,7 @@ void AudioSetDevice(const char *device)
|
||||
**
|
||||
** @note this is currently usable with alsa only.
|
||||
*/
|
||||
void AudioSetDeviceAC3(const char *device)
|
||||
void AudioSetPassthroughDevice(const char *device)
|
||||
{
|
||||
if (!AudioModuleName) {
|
||||
AudioModuleName = "alsa"; // detect alsa/OSS
|
||||
@ -2732,7 +2735,7 @@ void AudioSetDeviceAC3(const char *device)
|
||||
AudioModuleName = "oss";
|
||||
}
|
||||
}
|
||||
AudioAC3Device = device;
|
||||
AudioPassthroughDevice = device;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2961,7 +2964,7 @@ static void PrintVersion(void)
|
||||
#ifdef GIT_REV
|
||||
"(GIT-" GIT_REV ")"
|
||||
#endif
|
||||
",\n\t(c) 2009 - 2012 by Johns\n"
|
||||
",\n\t(c) 2009 - 2013 by Johns\n"
|
||||
"\tLicense AGPLv3: GNU Affero General Public License version 3\n");
|
||||
}
|
||||
|
||||
|
6
audio.h
6
audio.h
@ -1,7 +1,7 @@
|
||||
///
|
||||
/// @file audio.h @brief Audio module headerfile
|
||||
///
|
||||
/// Copyright (c) 2009 - 2012 by Johns. All Rights Reserved.
|
||||
/// Copyright (c) 2009 - 2013 by Johns. All Rights Reserved.
|
||||
///
|
||||
/// Contributor(s):
|
||||
///
|
||||
@ -48,7 +48,9 @@ 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
|
||||
|
||||
/// set pass-through device
|
||||
extern void AudioSetPassthroughDevice(const char *);
|
||||
extern void AudioSetChannel(const char *); ///< set mixer channel
|
||||
extern void AudioInit(void); ///< setup audio module
|
||||
extern void AudioExit(void); ///< cleanup and exit audio module
|
||||
|
410
codec.c
410
codec.c
@ -1,7 +1,7 @@
|
||||
///
|
||||
/// @file codec.c @brief Codec functions
|
||||
///
|
||||
/// Copyright (c) 2009 - 2012 by Johns. All Rights Reserved.
|
||||
/// Copyright (c) 2009 - 2013 by Johns. All Rights Reserved.
|
||||
///
|
||||
/// Contributor(s):
|
||||
///
|
||||
@ -30,13 +30,13 @@
|
||||
/// many bugs and incompatiblity in it. Don't use this shit.
|
||||
///
|
||||
|
||||
/// compile with passthrough support (stable, ac3 only)
|
||||
/// compile with pass-through support (stable, AC-3, E-AC-3 only)
|
||||
#define USE_PASSTHROUGH
|
||||
/// compile audio drift correction support (experimental)
|
||||
/// compile audio drift correction support (very experimental)
|
||||
#define USE_AUDIO_DRIFT_CORRECTION
|
||||
/// compile AC3 audio drift correction support (experimental)
|
||||
/// compile AC-3 audio drift correction support (very experimental)
|
||||
#define USE_AC3_DRIFT_CORRECTION
|
||||
/// use ffmpeg libswresample API
|
||||
/// use ffmpeg libswresample API (autodected, Makefile)
|
||||
#define noUSE_SWRESAMPLE
|
||||
|
||||
#include <stdio.h>
|
||||
@ -633,7 +633,7 @@ struct _audio_decoder_
|
||||
AVCodec *AudioCodec; ///< audio codec
|
||||
AVCodecContext *AudioCtx; ///< audio codec context
|
||||
|
||||
int PassthroughAC3; ///< current ac-3 pass-through
|
||||
char Passthrough; ///< current pass-through flags
|
||||
int SampleRate; ///< current stream sample rate
|
||||
int Channels; ///< current stream channels
|
||||
|
||||
@ -651,6 +651,10 @@ struct _audio_decoder_
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint16_t Spdif[24576 / 2]; ///< SPDIF output buffer
|
||||
int SpdifIndex; ///< index into SPDIF output buffer
|
||||
int SpdifCount; ///< SPDIF repeat counter
|
||||
|
||||
int64_t LastDelay; ///< last delay
|
||||
struct timespec LastTime; ///< last time
|
||||
int64_t LastPTS; ///< last PTS
|
||||
@ -670,24 +674,32 @@ struct _audio_decoder_
|
||||
#endif
|
||||
};
|
||||
|
||||
///
|
||||
/// IEC Data type enumeration.
|
||||
///
|
||||
enum IEC61937
|
||||
{
|
||||
IEC61937_AC3 = 0x01, ///< AC-3 data
|
||||
// FIXME: more data types
|
||||
IEC61937_EAC3 = 0x15, ///< E-AC-3 data
|
||||
};
|
||||
|
||||
#ifdef USE_AUDIO_DRIFT_CORRECTION
|
||||
#define CORRECT_PCM 1 ///< do PCM audio-drift correction
|
||||
#define CORRECT_AC3 2 ///< do AC3§ audio-drift correction
|
||||
#define CORRECT_AC3 2 ///< do AC3 audio-drift correction
|
||||
static char CodecAudioDrift; ///< flag: enable audio-drift correction
|
||||
#else
|
||||
static const int CodecAudioDrift = 0;
|
||||
#endif
|
||||
#ifdef USE_PASSTHROUGH
|
||||
//static char CodecPassthroughPCM; ///< pass pcm through (unsupported)
|
||||
static char CodecPassthroughAC3; ///< pass ac3 through
|
||||
|
||||
//static char CodecPassthroughDTS; ///< pass dts through (unsupported)
|
||||
//static char CodecPassthroughMPA; ///< pass mpa through (unsupported)
|
||||
///
|
||||
/// Pass-through flags: CodecPCM, CodecAC3, CodecEAC3, ...
|
||||
///
|
||||
static char CodecPassthrough;
|
||||
#else
|
||||
|
||||
static const int CodecPassthroughAC3 = 0;
|
||||
static const int CodecPassthrough = 0;
|
||||
#endif
|
||||
static char CodecDownmix; ///< enable ac-3 downmix
|
||||
static char CodecDownmix; ///< enable AC-3 decoder downmix
|
||||
|
||||
/**
|
||||
** Allocate a new audio decoder context.
|
||||
@ -840,7 +852,7 @@ void CodecAudioClose(AudioDecoder * audio_decoder)
|
||||
void CodecSetAudioDrift(int mask)
|
||||
{
|
||||
#ifdef USE_AUDIO_DRIFT_CORRECTION
|
||||
CodecAudioDrift = mask & 3;
|
||||
CodecAudioDrift = mask & (CORRECT_PCM | CORRECT_AC3);
|
||||
#endif
|
||||
(void)mask;
|
||||
}
|
||||
@ -848,12 +860,12 @@ void CodecSetAudioDrift(int mask)
|
||||
/**
|
||||
** Set audio pass-through.
|
||||
**
|
||||
** @param mask enable mask (PCM, AC3)
|
||||
** @param mask enable mask (PCM, AC3, EAC3)
|
||||
*/
|
||||
void CodecSetAudioPassthrough(int mask)
|
||||
{
|
||||
#ifdef USE_PASSTHROUGH
|
||||
CodecPassthroughAC3 = mask & 1 ? 1 : 0;
|
||||
CodecPassthrough = mask & (CodecPCM | CodecAC3 | CodecEAC3);
|
||||
#endif
|
||||
(void)mask;
|
||||
}
|
||||
@ -932,6 +944,178 @@ static void CodecReorderAudioFrame(int16_t * buf, int size, int channels)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** Handle audio format changes helper.
|
||||
**
|
||||
** @param audio_decoder audio decoder data
|
||||
** @param[out] passthrough pass-through output
|
||||
*/
|
||||
static int CodecAudioUpdateHelper(AudioDecoder * audio_decoder,
|
||||
int *passthrough)
|
||||
{
|
||||
const AVCodecContext *audio_ctx;
|
||||
int err;
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
Debug(3, "codec/audio: format change %s %dHz *%d channels%s%s%s%s%s\n",
|
||||
av_get_sample_fmt_name(audio_ctx->sample_fmt), audio_ctx->sample_rate,
|
||||
audio_ctx->channels, CodecPassthrough & CodecPCM ? " PCM" : "",
|
||||
CodecPassthrough & CodecMPA ? " MPA" : "",
|
||||
CodecPassthrough & CodecAC3 ? " AC3" : "",
|
||||
CodecPassthrough & CodecEAC3 ? " EAC3" : "",
|
||||
CodecPassthrough ? " pass-through" : "");
|
||||
|
||||
*passthrough = 0;
|
||||
audio_decoder->SampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->Channels = audio_ctx->channels;
|
||||
audio_decoder->HwChannels = audio_ctx->channels;
|
||||
audio_decoder->Passthrough = CodecPassthrough;
|
||||
|
||||
// SPDIF/HDMI pass-through
|
||||
if ((CodecPassthrough & CodecAC3 && audio_ctx->codec_id == CODEC_ID_AC3)
|
||||
|| (CodecPassthrough & CodecEAC3
|
||||
&& audio_ctx->codec_id == CODEC_ID_EAC3)) {
|
||||
audio_decoder->HwChannels = 2;
|
||||
audio_decoder->SpdifIndex = 0; // reset buffer
|
||||
audio_decoder->SpdifCount = 0;
|
||||
*passthrough = 1;
|
||||
}
|
||||
// channels not support?
|
||||
if ((err =
|
||||
AudioSetup(&audio_decoder->HwSampleRate,
|
||||
&audio_decoder->HwChannels, *passthrough))) {
|
||||
|
||||
Debug(3, "codec/audio: audio setup error\n");
|
||||
// FIXME: handle errors
|
||||
audio_decoder->HwChannels = 0;
|
||||
audio_decoder->HwSampleRate = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
Debug(3, "codec/audio: resample %s %dHz *%d -> %s %dHz *%d\n",
|
||||
av_get_sample_fmt_name(audio_ctx->sample_fmt), audio_ctx->sample_rate,
|
||||
audio_ctx->channels, av_get_sample_fmt_name(AV_SAMPLE_FMT_S16),
|
||||
audio_decoder->HwSampleRate, audio_decoder->HwChannels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
** Audio pass-through decoder helper.
|
||||
**
|
||||
** @param audio_decoder audio decoder data
|
||||
** @param avpkt undecoded audio packet
|
||||
*/
|
||||
static int CodecAudioPassthroughHelper(AudioDecoder * audio_decoder,
|
||||
const AVPacket * avpkt)
|
||||
{
|
||||
#ifdef USE_PASSTHROUGH
|
||||
const AVCodecContext *audio_ctx;
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthrough & CodecAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
uint16_t *spdif;
|
||||
int spdif_sz;
|
||||
|
||||
spdif = audio_decoder->Spdif;
|
||||
spdif_sz = 6144;
|
||||
|
||||
#ifdef USE_AC3_DRIFT_CORRECTION
|
||||
// FIXME: this works with some TVs/AVReceivers
|
||||
// FIXME: write burst size drift correction, which should work with all
|
||||
if (CodecAudioDrift & CORRECT_AC3) {
|
||||
int x;
|
||||
|
||||
x = (audio_decoder->DriftFrac +
|
||||
(audio_decoder->DriftCorr * spdif_sz)) / (10 *
|
||||
audio_decoder->HwSampleRate * 100);
|
||||
audio_decoder->DriftFrac =
|
||||
(audio_decoder->DriftFrac +
|
||||
(audio_decoder->DriftCorr * spdif_sz)) % (10 *
|
||||
audio_decoder->HwSampleRate * 100);
|
||||
// round to word border
|
||||
x *= audio_decoder->HwChannels * 4;
|
||||
if (x < -64) { // limit correction
|
||||
x = -64;
|
||||
} else if (x > 64) {
|
||||
x = 64;
|
||||
}
|
||||
spdif_sz += x;
|
||||
}
|
||||
#endif
|
||||
|
||||
// build SPDIF header and append A52 audio to it
|
||||
// avpkt is the original data
|
||||
if (spdif_sz < avpkt->size + 8) {
|
||||
Error(_("codec/audio: decoded data smaller than encoded\n"));
|
||||
return -1;
|
||||
}
|
||||
spdif[0] = htole16(0xF872); // iec 61937 sync word
|
||||
spdif[1] = htole16(0x4E1F);
|
||||
spdif[2] = htole16(IEC61937_AC3 | (avpkt->data[5] & 0x07) << 8);
|
||||
spdif[3] = htole16(avpkt->size * 8);
|
||||
// copy original data for output
|
||||
// FIXME: not 100% sure, if endian is correct on not intel hardware
|
||||
swab(avpkt->data, spdif + 4, avpkt->size);
|
||||
// FIXME: don't need to clear always
|
||||
memset(spdif + 4 + avpkt->size / 2, 0, spdif_sz - 8 - avpkt->size);
|
||||
// don't play with the ac-3 samples
|
||||
AudioEnqueue(spdif, spdif_sz);
|
||||
return 1;
|
||||
}
|
||||
if (CodecPassthrough & CodecEAC3 && audio_ctx->codec_id == CODEC_ID_EAC3) {
|
||||
uint16_t *spdif;
|
||||
int spdif_sz;
|
||||
int repeat;
|
||||
|
||||
// build SPDIF header and append A52 audio to it
|
||||
// avpkt is the original data
|
||||
spdif = audio_decoder->Spdif;
|
||||
spdif_sz = 6144;
|
||||
// 24576 = 4 * 6144
|
||||
if (spdif_sz < audio_decoder->SpdifIndex + avpkt->size + 8) {
|
||||
Error(_("codec/audio: decoded data smaller than encoded\n"));
|
||||
return -1;
|
||||
}
|
||||
// check if we must pack multiple packets
|
||||
repeat = 1;
|
||||
if ((avpkt->data[4] & 0xc0) != 0xc0) { // fscod
|
||||
static const uint8_t eac3_repeat[4] = { 6, 3, 2, 1 };
|
||||
|
||||
// fscod2
|
||||
repeat = eac3_repeat[(avpkt->data[4] & 0x30) >> 4];
|
||||
}
|
||||
//fprintf(stderr, "repeat %d\n", repeat);
|
||||
|
||||
// copy original data for output
|
||||
// pack upto repeat EAC-3 pakets into one IEC 61937 burst
|
||||
// FIXME: not 100% sure, if endian is correct on not intel hardware
|
||||
swab(avpkt->data, spdif + 4 + audio_decoder->SpdifIndex, avpkt->size);
|
||||
audio_decoder->SpdifIndex += avpkt->size;
|
||||
if (++audio_decoder->SpdifCount < repeat) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
spdif[0] = htole16(0xF872); // iec 61937 sync word
|
||||
spdif[1] = htole16(0x4E1F);
|
||||
spdif[2] = htole16(IEC61937_EAC3);
|
||||
spdif[3] = htole16(audio_decoder->SpdifIndex * 8);
|
||||
memset(spdif + 4 + audio_decoder->SpdifIndex / 2, 0,
|
||||
spdif_sz - 8 - audio_decoder->SpdifIndex);
|
||||
|
||||
// don't play with the eac-3 samples
|
||||
AudioEnqueue(spdif, spdif_sz);
|
||||
|
||||
audio_decoder->SpdifIndex = 0;
|
||||
audio_decoder->SpdifCount = 0;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef USE_SWRESAMPLE
|
||||
|
||||
/**
|
||||
@ -1007,8 +1191,10 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
|
||||
audio_decoder->Drift = drift;
|
||||
corr = (10 * audio_decoder->HwSampleRate * drift) / (90 * 1000);
|
||||
// SPDIF/HDMI passthrough
|
||||
if ((CodecAudioDrift & 2) && (!CodecPassthroughAC3
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_AC3)) {
|
||||
if ((CodecAudioDrift & CORRECT_AC3) && (!CodecPassthroughAC3
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_AC3)
|
||||
&& (!CodecPassthroughEAC3
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_EAC3)) {
|
||||
audio_decoder->DriftCorr = -corr;
|
||||
}
|
||||
|
||||
@ -1045,14 +1231,15 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
|
||||
** Handle audio format changes.
|
||||
**
|
||||
** @param audio_decoder audio decoder data
|
||||
**
|
||||
** @note this is the old not good supported version
|
||||
*/
|
||||
static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
{
|
||||
int passthrough;
|
||||
const AVCodecContext *audio_ctx;
|
||||
int err;
|
||||
int isAC3;
|
||||
|
||||
// FIXME: use swr_convert from swresample (only in ffmpeg!)
|
||||
if (audio_decoder->ReSample) {
|
||||
audio_resample_close(audio_decoder->ReSample);
|
||||
audio_decoder->ReSample = NULL;
|
||||
@ -1064,28 +1251,8 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
}
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
Debug(3, "codec/audio: format change %dHz %d channels %s\n",
|
||||
audio_ctx->sample_rate, audio_ctx->channels,
|
||||
CodecPassthroughAC3 ? "pass-through" : "");
|
||||
if ((err = CodecAudioUpdateHelper(audio_decoder, &passthrough))) {
|
||||
|
||||
audio_decoder->SampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->Channels = audio_ctx->channels;
|
||||
audio_decoder->PassthroughAC3 = CodecPassthroughAC3;
|
||||
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
audio_decoder->HwChannels = 2;
|
||||
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);
|
||||
@ -1101,19 +1268,21 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
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;
|
||||
}
|
||||
if (passthrough) { // pass-through no conversion allowed
|
||||
return;
|
||||
}
|
||||
// prepare audio drift resample
|
||||
#ifdef USE_AUDIO_DRIFT_CORRECTION
|
||||
if ((CodecAudioDrift & 1) && !isAC3) {
|
||||
if (CodecAudioDrift & CORRECT_PCM) {
|
||||
if (audio_decoder->AvResample) {
|
||||
Error(_("codec/audio: overwrite resample\n"));
|
||||
}
|
||||
@ -1144,7 +1313,7 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
void CodecAudioEnqueue(AudioDecoder * audio_decoder, int16_t * data, int count)
|
||||
{
|
||||
#ifdef USE_AUDIO_DRIFT_CORRECTION
|
||||
if ((CodecAudioDrift & 1) && audio_decoder->AvResample) {
|
||||
if ((CodecAudioDrift & CORRECT_PCM) && 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];
|
||||
@ -1205,12 +1374,16 @@ void CodecAudioEnqueue(AudioDecoder * audio_decoder, int16_t * data, int count)
|
||||
n *= 2;
|
||||
|
||||
n *= audio_decoder->HwChannels;
|
||||
if (!(audio_decoder->Passthrough & CodecPCM)) {
|
||||
CodecReorderAudioFrame(buf, n, audio_decoder->HwChannels);
|
||||
}
|
||||
AudioEnqueue(buf, n);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (!(audio_decoder->Passthrough & CodecPCM)) {
|
||||
CodecReorderAudioFrame(data, count, audio_decoder->HwChannels);
|
||||
}
|
||||
AudioEnqueue(data, count);
|
||||
}
|
||||
|
||||
@ -1232,6 +1405,7 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
|
||||
// FIXME: don't need to decode pass-through codecs
|
||||
buf_sz = sizeof(buf);
|
||||
l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, (AVPacket *) avpkt);
|
||||
if (avpkt->size != l) {
|
||||
@ -1250,7 +1424,7 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
CodecAudioSetClock(audio_decoder, avpkt->pts);
|
||||
}
|
||||
// FIXME: must first play remainings bytes, than change and play new.
|
||||
if (audio_decoder->PassthroughAC3 != CodecPassthroughAC3
|
||||
if (audio_decoder->Passthrough != CodecPassthrough
|
||||
|| audio_decoder->SampleRate != audio_ctx->sample_rate
|
||||
|| audio_decoder->Channels != audio_ctx->channels) {
|
||||
CodecAudioUpdateFormat(audio_decoder);
|
||||
@ -1283,48 +1457,7 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
CodecAudioEnqueue(audio_decoder, outbuf, outlen);
|
||||
}
|
||||
} else {
|
||||
#ifdef USE_PASSTHROUGH
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
// build SPDIF header and append A52 audio to it
|
||||
// avpkt is the original data
|
||||
buf_sz = 6144;
|
||||
|
||||
#ifdef USE_AC3_DRIFT_CORRECTION
|
||||
if (CodecAudioDrift & 2) {
|
||||
int x;
|
||||
|
||||
x = (audio_decoder->DriftFrac +
|
||||
(audio_decoder->DriftCorr * buf_sz)) / (10 *
|
||||
audio_decoder->HwSampleRate * 100);
|
||||
audio_decoder->DriftFrac =
|
||||
(audio_decoder->DriftFrac +
|
||||
(audio_decoder->DriftCorr * buf_sz)) % (10 *
|
||||
audio_decoder->HwSampleRate * 100);
|
||||
x *= audio_decoder->HwChannels * 4;
|
||||
if (x < -64) { // limit correction
|
||||
x = -64;
|
||||
} else if (x > 64) {
|
||||
x = 64;
|
||||
}
|
||||
buf_sz += x;
|
||||
}
|
||||
#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);
|
||||
if (CodecAudioPassthroughHelper(audio_decoder, avpkt)) {
|
||||
return;
|
||||
}
|
||||
#if 0
|
||||
@ -1377,7 +1510,6 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
}
|
||||
// DTS HD?
|
||||
// True HD?
|
||||
#endif
|
||||
#endif
|
||||
CodecAudioEnqueue(audio_decoder, buf, buf_sz);
|
||||
}
|
||||
@ -1461,8 +1593,10 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
|
||||
audio_decoder->Drift = drift;
|
||||
corr = (10 * audio_decoder->HwSampleRate * drift) / (90 * 1000);
|
||||
// SPDIF/HDMI passthrough
|
||||
if ((CodecAudioDrift & 2) && (!CodecPassthroughAC3
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_AC3)) {
|
||||
if ((CodecAudioDrift & CORRECT_AC3) && (!(CodecPassthrough & CodecAC3)
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_AC3)
|
||||
&& (!(CodecPassthrough & CodecEAC3)
|
||||
|| audio_decoder->AudioCtx->codec_id != CODEC_ID_EAC3)) {
|
||||
audio_decoder->DriftCorr = -corr;
|
||||
}
|
||||
|
||||
@ -1504,49 +1638,27 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
|
||||
*/
|
||||
static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
{
|
||||
int passthrough;
|
||||
const AVCodecContext *audio_ctx;
|
||||
int err;
|
||||
int isAC3;
|
||||
|
||||
if (CodecAudioUpdateHelper(audio_decoder, &passthrough)) {
|
||||
// FIXME: handle swresample format conversions.
|
||||
return;
|
||||
}
|
||||
if (passthrough) { // pass-through no conversion allowed
|
||||
return;
|
||||
}
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
Debug(3, "codec/audio: format change %s %dHz *%d channels %s\n",
|
||||
av_get_sample_fmt_name(audio_ctx->sample_fmt), audio_ctx->sample_rate,
|
||||
audio_ctx->channels, CodecPassthroughAC3 ? "pass-through" : "");
|
||||
|
||||
audio_decoder->SampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->Channels = audio_ctx->channels;
|
||||
audio_decoder->PassthroughAC3 = CodecPassthroughAC3;
|
||||
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
audio_decoder->HwChannels = 2;
|
||||
isAC3 = 1;
|
||||
} else {
|
||||
audio_decoder->HwChannels = audio_ctx->channels;
|
||||
isAC3 = 0;
|
||||
#ifdef DEBUG
|
||||
if (audio_ctx->sample_fmt == AV_SAMPLE_FMT_S16
|
||||
&& audio_ctx->sample_rate == audio_decoder->HwSampleRate
|
||||
&& !CodecAudioDrift) {
|
||||
// FIXME: use Resample only, when it is needed!
|
||||
fprintf(stderr, "no resample needed\n");
|
||||
}
|
||||
|
||||
// channels not support?
|
||||
if ((err =
|
||||
AudioSetup(&audio_decoder->HwSampleRate,
|
||||
&audio_decoder->HwChannels, isAC3))) {
|
||||
|
||||
Debug(3, "codec/audio: audio setup error\n");
|
||||
// FIXME: handle errors
|
||||
audio_decoder->HwChannels = 0;
|
||||
audio_decoder->HwSampleRate = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAC3) { // no AC3 conversion allowed
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(3, "codec/audio: resample %s %dHz *%d -> %s %dHz *%d\n",
|
||||
av_get_sample_fmt_name(audio_ctx->sample_fmt), audio_ctx->sample_rate,
|
||||
audio_ctx->channels, av_get_sample_fmt_name(AV_SAMPLE_FMT_S16),
|
||||
audio_decoder->HwSampleRate, audio_decoder->HwChannels);
|
||||
#endif
|
||||
|
||||
audio_decoder->Resample =
|
||||
swr_alloc_set_opts(audio_decoder->Resample, audio_ctx->channel_layout,
|
||||
@ -1579,6 +1691,7 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
|
||||
// FIXME: don't need to decode pass-through codecs
|
||||
frame.data[0] = NULL;
|
||||
n = avcodec_decode_audio4(audio_ctx, &frame, &got_frame,
|
||||
(AVPacket *) avpkt);
|
||||
@ -1602,42 +1715,20 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
CodecAudioSetClock(audio_decoder, avpkt->pts);
|
||||
}
|
||||
// format change
|
||||
if (audio_decoder->PassthroughAC3 != CodecPassthroughAC3
|
||||
if (audio_decoder->Passthrough != CodecPassthrough
|
||||
|| audio_decoder->SampleRate != audio_ctx->sample_rate
|
||||
|| audio_decoder->Channels != audio_ctx->channels) {
|
||||
CodecAudioUpdateFormat(audio_decoder);
|
||||
|
||||
}
|
||||
|
||||
if (!audio_decoder->HwSampleRate || !audio_decoder->HwChannels) {
|
||||
return; // unsupported sample format
|
||||
}
|
||||
#ifdef USE_PASSTHROUGH
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
int16_t spdif[6144 / 2];
|
||||
int spdif_sz;
|
||||
|
||||
// build SPDIF header and append A52 audio to it
|
||||
// avpkt is the original data
|
||||
spdif_sz = 6144;
|
||||
if (spdif_sz < avpkt->size + 8) {
|
||||
Error(_("codec/audio: decoded data smaller than encoded\n"));
|
||||
if (CodecAudioPassthroughHelper(audio_decoder, avpkt)) {
|
||||
return;
|
||||
}
|
||||
// copy original data for output
|
||||
spdif[0] = htole16(0xF872); // iec 61937 sync word
|
||||
spdif[1] = htole16(0x4E1F);
|
||||
spdif[2] = htole16(0x01 | (avpkt->data[5] & 0x07) << 8);
|
||||
spdif[3] = htole16(avpkt->size * 8);
|
||||
// FIXME: not 100% sure, if endian is correct on not intel hardware
|
||||
swab(avpkt->data, spdif + 4, avpkt->size);
|
||||
memset(spdif + 4 + avpkt->size / 2, 0, spdif_sz - 8 - avpkt->size);
|
||||
// don't play with the ac-3 samples
|
||||
AudioEnqueue(spdif, spdif_sz);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (0) {
|
||||
char strbuf[32];
|
||||
int data_sz;
|
||||
@ -1665,12 +1756,19 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
sizeof(outbuf) / (2 * audio_decoder->HwChannels),
|
||||
(const uint8_t **)frame.extended_data, frame.nb_samples);
|
||||
if (n > 0) {
|
||||
if (!(audio_decoder->Passthrough & CodecPCM)) {
|
||||
CodecReorderAudioFrame((int16_t *) outbuf,
|
||||
n * 2 * audio_decoder->HwChannels, audio_decoder->HwChannels);
|
||||
n * 2 * audio_decoder->HwChannels,
|
||||
audio_decoder->HwChannels);
|
||||
}
|
||||
AudioEnqueue(outbuf, n * 2 * audio_decoder->HwChannels);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
// should be never reached
|
||||
fprintf(stderr, "oops\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
12
codec.h
12
codec.h
@ -1,7 +1,7 @@
|
||||
///
|
||||
/// @file codec.h @brief Codec module headerfile
|
||||
///
|
||||
/// Copyright (c) 2009 - 2012 by Johns. All Rights Reserved.
|
||||
/// Copyright (c) 2009 - 2013 by Johns. All Rights Reserved.
|
||||
///
|
||||
/// Contributor(s):
|
||||
///
|
||||
@ -23,6 +23,16 @@
|
||||
/// @addtogroup Codec
|
||||
/// @{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Defines
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#define CodecPCM 0x01 ///< PCM bit mask
|
||||
#define CodecMPA 0x02 ///< MPA bit mask (planned)
|
||||
#define CodecAC3 0x04 ///< AC-3 bit mask
|
||||
#define CodecEAC3 0x08 ///< EAC-3 bit mask
|
||||
#define CodecDTS 0x10 ///< DTS bit mask (planned)
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//----------------------------------------------------------------------------
|
||||
|
65
softhddev.c
65
softhddev.c
@ -314,7 +314,7 @@ const uint16_t Ac3FrameSizeTable[38][3] = {
|
||||
};
|
||||
|
||||
///
|
||||
/// Fast check for AC3 audio.
|
||||
/// Fast check for (E)AC3 audio.
|
||||
///
|
||||
/// 5 bytes 0x0B77xxxxxx AC3 audio
|
||||
///
|
||||
@ -326,17 +326,11 @@ static inline int FastAc3Check(const uint8_t * p)
|
||||
if (p[1] != 0x77) {
|
||||
return 0;
|
||||
}
|
||||
if ((p[4] & 0xC0) == 0xC0) { // invalid sample rate
|
||||
return 0;
|
||||
}
|
||||
if ((p[4] & 0x3F) > 37) { // invalid frame size
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// Check for AC-3 audio.
|
||||
/// Check for (E)AC-3 audio.
|
||||
///
|
||||
/// 0x0B77xxxxxx already checked.
|
||||
///
|
||||
@ -347,20 +341,61 @@ static inline int FastAc3Check(const uint8_t * p)
|
||||
/// @retval 0 no valid AC-3 audio
|
||||
/// @retval >0 valid AC-3 audio
|
||||
///
|
||||
/// o AC3 Header
|
||||
/// AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF
|
||||
///
|
||||
/// o a 16x Frame sync, always 0x0B77
|
||||
/// o b 16x CRC 16
|
||||
/// o c 2x Samplerate
|
||||
/// o d 6x Framesize code
|
||||
/// o e 5x Bitstream ID
|
||||
/// o f 3x Bitstream mode
|
||||
///
|
||||
/// o EAC3 Header
|
||||
/// AAAAAAAA AAAAAAAA BBCCCDDD DDDDDDDD EEFFGGGH IIIII...
|
||||
///
|
||||
/// o a 16x Frame sync, always 0x0B77
|
||||
/// o b 2x Frame type
|
||||
/// o c 3x Sub stream ID
|
||||
/// o d 10x Framesize - 1 in words
|
||||
/// o e 2x Framesize code
|
||||
/// o f 2x Framesize code 2
|
||||
///
|
||||
static int Ac3Check(const uint8_t * data, int size)
|
||||
{
|
||||
int frame_size;
|
||||
|
||||
if (size < 5) { // need 5 bytes to see if AC3/EAC3
|
||||
return -5;
|
||||
}
|
||||
|
||||
if (data[5] > (10 << 3)) { // EAC3
|
||||
if ((data[4] & 0xF0) == 0xF0) { // invalid fscod fscod2
|
||||
return 0;
|
||||
}
|
||||
frame_size = ((data[2] & 0x03) << 8) + data[3] + 1;
|
||||
frame_size *= 2;
|
||||
} else { // AC3
|
||||
int fscod;
|
||||
int frmsizcod;
|
||||
int frame_size;
|
||||
|
||||
// crc1 crc1 fscod|frmsizcod
|
||||
fscod = data[4] >> 6;
|
||||
frmsizcod = data[4] & 0x3F; // invalid is checked by fast
|
||||
if (fscod == 0x03) { // invalid sample rate
|
||||
return 0;
|
||||
}
|
||||
frmsizcod = data[4] & 0x3F;
|
||||
if (frmsizcod > 37) { // invalid frame size
|
||||
return 0;
|
||||
}
|
||||
// invalid is checked above
|
||||
frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2;
|
||||
}
|
||||
|
||||
if (frame_size + 5 > size) {
|
||||
return -frame_size - 5;
|
||||
}
|
||||
// FIXME: relaxed checks if codec is already detected
|
||||
// check if after this frame a new AC-3 frame starts
|
||||
if (FastAc3Check(data + frame_size)) {
|
||||
return frame_size;
|
||||
@ -617,6 +652,7 @@ static void PesParse(PesDemux * pesdx, const uint8_t * data, int size,
|
||||
|
||||
// 4 bytes 0xFFExxxxx Mpeg audio
|
||||
// 5 bytes 0x0B77xxxxxx AC3 audio
|
||||
// 6 bytes 0x0B77xxxxxxxx EAC3 audio
|
||||
// 3 bytes 0x56Exxx AAC LATM audio
|
||||
// 7/9 bytes 0xFFFxxxxxxxxxxx ADTS audio
|
||||
// PCM audio can't be found
|
||||
@ -629,6 +665,9 @@ static void PesParse(PesDemux * pesdx, const uint8_t * data, int size,
|
||||
if (!r && FastAc3Check(q)) {
|
||||
r = Ac3Check(q, n);
|
||||
codec_id = CODEC_ID_AC3;
|
||||
if (r > 0 && q[5] > (10 << 3)) {
|
||||
codec_id = CODEC_ID_EAC3;
|
||||
}
|
||||
}
|
||||
if (!r && FastLatmCheck(q)) {
|
||||
r = LatmCheck(q, n);
|
||||
@ -1119,6 +1158,7 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
|
||||
// 4 bytes 0xFFExxxxx Mpeg audio
|
||||
// 3 bytes 0x56Exxx AAC LATM audio
|
||||
// 5 bytes 0x0B77xxxxxx AC3 audio
|
||||
// 6 bytes 0x0B77xxxxxxxx EAC3 audio
|
||||
// 7/9 bytes 0xFFFxxxxxxxxxxx ADTS audio
|
||||
// PCM audio can't be found
|
||||
r = 0;
|
||||
@ -1134,6 +1174,9 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
|
||||
if ((id == 0xbd || (id & 0xF0) == 0x80) && !r && FastAc3Check(p)) {
|
||||
r = Ac3Check(p, n);
|
||||
codec_id = CODEC_ID_AC3;
|
||||
if (r > 0 && p[5] > (10 << 3)) {
|
||||
codec_id = CODEC_ID_EAC3;
|
||||
}
|
||||
/* faster ac3 detection at end of pes packet (no improvemnts)
|
||||
if (AudioCodecID == codec_id && -r - 2 == n) {
|
||||
r = n;
|
||||
@ -2787,7 +2830,7 @@ int ProcessArgs(int argc, char *const argv[])
|
||||
AudioSetChannel(optarg);
|
||||
continue;
|
||||
case 'p': // pass-through audio device
|
||||
AudioSetDeviceAC3(optarg);
|
||||
AudioSetPassthroughDevice(optarg);
|
||||
continue;
|
||||
case 'd': // x11 display name
|
||||
X11DisplayName = optarg;
|
||||
|
@ -106,6 +106,8 @@ extern "C"
|
||||
extern void PipStop(void);
|
||||
/// Pip play video packet
|
||||
extern int PipPlayVideo(const uint8_t *, int);
|
||||
|
||||
extern const char *X11DisplayName; ///< x11 display name
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -20,6 +20,8 @@
|
||||
/// $Id$
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define __STDC_CONSTANT_MACROS ///< needed for ffmpeg UINT64_C
|
||||
|
||||
#include <vdr/interface.h>
|
||||
#include <vdr/plugin.h>
|
||||
#include <vdr/player.h>
|
||||
@ -34,15 +36,15 @@
|
||||
#include "softhddev.h"
|
||||
#include "softhddevice.h"
|
||||
#include "softhddevice_service.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <stdint.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
extern const char *X11DisplayName; ///< x11 display name
|
||||
|
||||
extern void CodecSetAudioDrift(int);
|
||||
extern void CodecSetAudioPassthrough(int);
|
||||
extern void CodecSetAudioDownmix(int);
|
||||
#include "codec.h"
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -126,7 +128,8 @@ static int ConfigAutoCropTolerance; ///< auto crop detection tolerance
|
||||
|
||||
static int ConfigVideoAudioDelay; ///< config audio delay
|
||||
static char ConfigAudioDrift; ///< config audio drift
|
||||
static char ConfigAudioPassthrough; ///< config audio pass-through
|
||||
static char ConfigAudioPassthrough; ///< config audio pass-through mask
|
||||
static char AudioPassthroughState; ///< flag audio pass-through on/off
|
||||
static char ConfigAudioDownmix; ///< config ffmpeg audio downmix
|
||||
static char ConfigAudioSoftvol; ///< config use software volume
|
||||
static char ConfigAudioNormalize; ///< config use normalize volume
|
||||
@ -138,7 +141,7 @@ int ConfigAudioBufferTime; ///< config size ms of audio buffer
|
||||
|
||||
static char *ConfigX11Display; ///< config x11 display
|
||||
static char *ConfigAudioDevice; ///< config audio stereo device
|
||||
static char *ConfigAC3Device; ///< config audio passthrough device
|
||||
static char *ConfigPassthroughDevice; ///< config audio pass-through device
|
||||
|
||||
#ifdef USE_PIP
|
||||
static int ConfigPipX = 100 - 3 - 18; ///< config pip pip x in %
|
||||
@ -615,7 +618,9 @@ class cMenuSetupSoft:public cMenuSetupPage
|
||||
int Audio;
|
||||
int AudioDelay;
|
||||
int AudioDrift;
|
||||
int AudioPassthrough;
|
||||
int AudioPassthroughPCM;
|
||||
int AudioPassthroughAC3;
|
||||
int AudioPassthroughEAC3;
|
||||
int AudioDownmix;
|
||||
int AudioSoftvol;
|
||||
int AudioNormalize;
|
||||
@ -720,9 +725,6 @@ void cMenuSetupSoft::Create(void)
|
||||
static const char *const audiodrift[] = {
|
||||
"None", "PCM", "AC-3", "PCM + AC-3"
|
||||
};
|
||||
static const char *const passthrough[] = {
|
||||
"None", "AC-3"
|
||||
};
|
||||
static const char *const resolution[RESOLUTIONS] = {
|
||||
"576i", "720p", "fake 1080i", "1080i"
|
||||
};
|
||||
@ -844,9 +846,13 @@ void cMenuSetupSoft::Create(void)
|
||||
-1000, 1000));
|
||||
Add(new cMenuEditStraItem(tr("Audio drift correction"), &AudioDrift, 4,
|
||||
audiodrift));
|
||||
Add(new cMenuEditStraItem(tr("Audio pass-through"), &AudioPassthrough,
|
||||
2, passthrough));
|
||||
Add(new cMenuEditBoolItem(tr("Enable AC-3 (decoder) downmix"),
|
||||
Add(new cMenuEditBoolItem(tr("Enable PCM pass-through"),
|
||||
&AudioPassthroughPCM, trVDR("no"), trVDR("yes")));
|
||||
Add(new cMenuEditBoolItem(tr("Enable AC-3 pass-through"),
|
||||
&AudioPassthroughAC3, trVDR("no"), trVDR("yes")));
|
||||
Add(new cMenuEditBoolItem(tr("Enable EAC-3 pass-through"),
|
||||
&AudioPassthroughEAC3, trVDR("no"), trVDR("yes")));
|
||||
Add(new cMenuEditBoolItem(tr("Enable (E)AC-3 (decoder) downmix"),
|
||||
&AudioDownmix, trVDR("no"), trVDR("yes")));
|
||||
Add(new cMenuEditBoolItem(tr("Volume control"), &AudioSoftvol,
|
||||
tr("Hardware"), tr("Software")));
|
||||
@ -1031,7 +1037,9 @@ cMenuSetupSoft::cMenuSetupSoft(void)
|
||||
Audio = 0;
|
||||
AudioDelay = ConfigVideoAudioDelay;
|
||||
AudioDrift = ConfigAudioDrift;
|
||||
AudioPassthrough = ConfigAudioPassthrough;
|
||||
AudioPassthroughPCM = ConfigAudioPassthrough & CodecPCM;
|
||||
AudioPassthroughAC3 = ConfigAudioPassthrough & CodecAC3;
|
||||
AudioPassthroughEAC3 = ConfigAudioPassthrough & CodecEAC3;
|
||||
AudioDownmix = ConfigAudioDownmix;
|
||||
AudioSoftvol = ConfigAudioSoftvol;
|
||||
AudioNormalize = ConfigAudioNormalize;
|
||||
@ -1174,8 +1182,12 @@ void cMenuSetupSoft::Store(void)
|
||||
VideoSetAudioDelay(ConfigVideoAudioDelay);
|
||||
SetupStore("AudioDrift", ConfigAudioDrift = AudioDrift);
|
||||
CodecSetAudioDrift(ConfigAudioDrift);
|
||||
SetupStore("AudioPassthrough", ConfigAudioPassthrough = AudioPassthrough);
|
||||
ConfigAudioPassthrough = (AudioPassthroughPCM ? CodecPCM : 0)
|
||||
| (AudioPassthroughAC3 ? CodecAC3 : 0)
|
||||
| (AudioPassthroughEAC3 ? CodecEAC3 : 0);
|
||||
SetupStore("AudioPassthrough", ConfigAudioPassthrough);
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough);
|
||||
AudioPassthroughState = 1;
|
||||
SetupStore("AudioDownmix", ConfigAudioDownmix = AudioDownmix);
|
||||
CodecSetAudioDownmix(ConfigAudioDownmix);
|
||||
SetupStore("AudioSoftvol", ConfigAudioSoftvol = AudioSoftvol);
|
||||
@ -1786,18 +1798,23 @@ static void HandleHotkey(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case 10: // disable pass-through
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough = 0);
|
||||
AudioPassthroughState = 0;
|
||||
CodecSetAudioPassthrough(0);
|
||||
Skins.QueueMessage(mtInfo, tr("pass-through disabled"));
|
||||
break;
|
||||
case 11: // enable pass-through
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough = 1);
|
||||
// note: you can't enable, without configured pass-through
|
||||
AudioPassthroughState = 1;
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough);
|
||||
Skins.QueueMessage(mtInfo, tr("pass-through enabled"));
|
||||
break;
|
||||
case 12: // toggle pass-through
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough ^= 1);
|
||||
if (ConfigAudioPassthrough) {
|
||||
AudioPassthroughState ^= 1;
|
||||
if (AudioPassthroughState) {
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough);
|
||||
Skins.QueueMessage(mtInfo, tr("pass-through enabled"));
|
||||
} else {
|
||||
CodecSetAudioPassthrough(0);
|
||||
Skins.QueueMessage(mtInfo, tr("pass-through disabled"));
|
||||
}
|
||||
break;
|
||||
@ -2902,6 +2919,9 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
|
||||
}
|
||||
if (!strcasecmp(name, "AudioPassthrough")) {
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough = atoi(value));
|
||||
if (ConfigAudioPassthrough) {
|
||||
AudioPassthroughState = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!strcasecmp(name, "AudioDownmix")) {
|
||||
@ -3272,13 +3292,13 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
|
||||
free(tmp);
|
||||
return "missing option argument";
|
||||
}
|
||||
free(ConfigAC3Device);
|
||||
ConfigAC3Device = strdup(o);
|
||||
AudioSetDeviceAC3(ConfigAC3Device);
|
||||
free(ConfigPassthroughDevice);
|
||||
ConfigPassthroughDevice = strdup(o);
|
||||
AudioSetPassthroughDevice(ConfigPassthroughDevice);
|
||||
} else if (!strncmp(s, "-p", 2)) {
|
||||
free(ConfigAC3Device);
|
||||
ConfigAC3Device = strdup(s + 2);
|
||||
AudioSetDeviceAC3(ConfigAC3Device);
|
||||
free(ConfigPassthroughDevice);
|
||||
ConfigPassthroughDevice = strdup(s + 2);
|
||||
AudioSetPassthroughDevice(ConfigPassthroughDevice);
|
||||
|
||||
} else if (*s) {
|
||||
free(tmp);
|
||||
|
Loading…
Reference in New Issue
Block a user