2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
14 #include "cores/AudioEngine/AESinkFactory.h"
15 #include "cores/AudioEngine/Utils/AEUtil.h"
16 #include "utils/log.h"
17 #include "threads/SingleLock.h"
20 #include <sys/ioctl.h>
21 #include <sys/fcntl.h>
23 #if defined(OSS4) || defined(TARGET_FREEBSD)
24 #include <sys/soundcard.h>
26 #include <linux/soundcard.h>
29 #define OSS_FRAMES 256
31 static enum AEChannel OSSChannelMap
[9] =
32 {AE_CH_FL
, AE_CH_FR
, AE_CH_BL
, AE_CH_BR
, AE_CH_FC
, AE_CH_LFE
, AE_CH_SL
, AE_CH_SR
, AE_CH_NULL
};
34 #if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
35 static int OSSSampleRateList
[] =
55 CAESinkOSS::CAESinkOSS()
60 CAESinkOSS::~CAESinkOSS()
65 void CAESinkOSS::Register()
67 AE::AESinkRegEntry entry
;
68 entry
.sinkName
= "OSS";
69 entry
.createFunc
= CAESinkOSS::Create
;
70 entry
.enumerateFunc
= CAESinkOSS::EnumerateDevicesEx
;
71 AE::CAESinkFactory::RegisterSink(entry
);
74 std::unique_ptr
<IAESink
> CAESinkOSS::Create(std::string
& device
, AEAudioFormat
& desiredFormat
)
76 auto sink
= std::make_unique
<CAESinkOSS
>();
77 if (sink
->Initialize(desiredFormat
, device
))
83 std::string
CAESinkOSS::GetDeviceUse(const AEAudioFormat
& format
, const std::string
&device
)
86 if (AE_IS_RAW(format
.m_dataFormat
))
88 if (device
.find_first_of('/') != 0)
89 return "/dev/dsp_ac3";
93 if (device
.find_first_of('/') != 0)
94 return "/dev/dsp_multich";
96 if (device
.find_first_of('/') != 0)
103 bool CAESinkOSS::Initialize(AEAudioFormat
&format
, std::string
&device
)
105 m_initFormat
= format
;
106 format
.m_channelLayout
= GetChannelLayout(format
);
107 device
= GetDeviceUse(format
, device
);
110 /* try to open in exclusive mode first (no software mixing) */
111 m_fd
= open(device
.c_str(), O_WRONLY
| O_EXCL
, 0);
114 m_fd
= open(device
.c_str(), O_WRONLY
, 0);
117 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to open the audio device: {}", device
);
122 if (ioctl(m_fd
, SNDCTL_DSP_GETFMTS
, &format_mask
) == -1)
125 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to get supported formats, assuming AFMT_S16_NE");
130 bool useCooked
= true;
135 if ((format
.m_dataFormat
== AE_FMT_FLOAT
) && (format_mask
& AFMT_FLOAT
))
136 oss_fmt
= AFMT_FLOAT
;
140 if ((format
.m_dataFormat
== AE_FMT_S32NE
) && (format_mask
& AFMT_S32_NE
))
141 oss_fmt
= AFMT_S32_NE
;
142 else if ((format
.m_dataFormat
== AE_FMT_S32BE
) && (format_mask
& AFMT_S32_BE
))
143 oss_fmt
= AFMT_S32_BE
;
144 else if ((format
.m_dataFormat
== AE_FMT_S32LE
) && (format_mask
& AFMT_S32_LE
))
145 oss_fmt
= AFMT_S32_LE
;
148 if ((format
.m_dataFormat
== AE_FMT_S16NE
) && (format_mask
& AFMT_S16_NE
))
149 oss_fmt
= AFMT_S16_NE
;
150 else if ((format
.m_dataFormat
== AE_FMT_S16BE
) && (format_mask
& AFMT_S16_BE
))
151 oss_fmt
= AFMT_S16_BE
;
152 else if ((format
.m_dataFormat
== AE_FMT_S16LE
) && (format_mask
& AFMT_S16_LE
))
153 oss_fmt
= AFMT_S16_LE
;
154 else if ((format
.m_dataFormat
== AE_FMT_U8
) && (format_mask
& AFMT_U8
))
156 else if (((format
.m_dataFormat
== AE_FMT_RAW
) ) && (format_mask
& AFMT_AC3
))
159 format
.m_dataFormat
= AE_FMT_S16NE
;
161 else if (format
.m_dataFormat
== AE_FMT_RAW
)
164 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to find a suitable RAW output format");
170 "CAESinkOSS::Initialize - Your hardware does not support {}, trying other formats",
171 CAEUtil::DataFormatToStr(format
.m_dataFormat
));
173 /* fallback to the best supported format */
175 if (format_mask
& AFMT_FLOAT
)
177 oss_fmt
= AFMT_FLOAT
;
178 format
.m_dataFormat
= AE_FMT_FLOAT
;
183 if (format_mask
& AFMT_S32_NE
)
185 oss_fmt
= AFMT_S32_NE
;
186 format
.m_dataFormat
= AE_FMT_S32NE
;
188 else if (format_mask
& AFMT_S32_BE
)
190 oss_fmt
= AFMT_S32_BE
;
191 format
.m_dataFormat
= AE_FMT_S32BE
;
193 else if (format_mask
& AFMT_S32_LE
)
195 oss_fmt
= AFMT_S32_LE
;
196 format
.m_dataFormat
= AE_FMT_S32LE
;
200 if (format_mask
& AFMT_S16_NE
)
202 oss_fmt
= AFMT_S16_NE
;
203 format
.m_dataFormat
= AE_FMT_S16NE
;
205 else if (format_mask
& AFMT_S16_BE
)
207 oss_fmt
= AFMT_S16_BE
;
208 format
.m_dataFormat
= AE_FMT_S16BE
;
210 else if (format_mask
& AFMT_S16_LE
)
212 oss_fmt
= AFMT_S16_LE
;
213 format
.m_dataFormat
= AE_FMT_S16LE
;
215 else if (format_mask
& AFMT_U8
)
218 format
.m_dataFormat
= AE_FMT_U8
;
222 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to find a suitable native output format, will try to use AE_FMT_S16NE anyway");
223 oss_fmt
= AFMT_S16_NE
;
224 format
.m_dataFormat
= AE_FMT_S16NE
;
226 /* dont use cooked if we did not find a native format, OSS might be able to convert */
236 if (ioctl(m_fd
, SNDCTL_DSP_COOKEDMODE
, &oss_cooked
) == -1)
237 CLog::Log(LOGWARNING
, "CAESinkOSS::Initialize - Failed to set cooked mode");
241 if (ioctl(m_fd
, SNDCTL_DSP_SETFMT
, &oss_fmt
) == -1)
244 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to set the data format ({})",
245 CAEUtil::DataFormatToStr(format
.m_dataFormat
));
249 /* find the number we need to open to access the channels we need */
252 for (int ch
= format
.m_channelLayout
.Count(); ch
< 9; ++ch
)
255 if (ioctl(m_fd
, SNDCTL_DSP_CHANNELS
, &oss_ch
) != -1 && oss_ch
>= (int)format
.m_channelLayout
.Count())
263 CLog::Log(LOGWARNING
, "CAESinkOSS::Initialize - Failed to access the number of channels required, falling back");
265 #if defined(TARGET_FREEBSD)
266 /* fix hdmi 8 channels order */
267 if ((oss_fmt
!= AFMT_AC3
) && 8 == oss_ch
)
269 unsigned long long order
= 0x0000000087346521ULL
;
271 if (ioctl(m_fd
, SNDCTL_DSP_SET_CHNORDER
, &order
) == -1)
272 CLog::Log(LOGWARNING
, "CAESinkOSS::Initialize - Failed to set the channel order");
275 unsigned long long order
= 0;
277 for (unsigned int i
= 0; i
< format
.m_channelLayout
.Count(); ++i
)
278 switch (format
.m_channelLayout
[i
])
280 case AE_CH_FL
: order
= (order
<< 4) | CHID_L
; break;
281 case AE_CH_FR
: order
= (order
<< 4) | CHID_R
; break;
282 case AE_CH_FC
: order
= (order
<< 4) | CHID_C
; break;
283 case AE_CH_LFE
: order
= (order
<< 4) | CHID_LFE
; break;
284 case AE_CH_SL
: order
= (order
<< 4) | CHID_LS
; break;
285 case AE_CH_SR
: order
= (order
<< 4) | CHID_RS
; break;
286 case AE_CH_BL
: order
= (order
<< 4) | CHID_LR
; break;
287 case AE_CH_BR
: order
= (order
<< 4) | CHID_RR
; break;
293 if (ioctl(m_fd
, SNDCTL_DSP_SET_CHNORDER
, &order
) == -1)
295 if (ioctl(m_fd
, SNDCTL_DSP_GET_CHNORDER
, &order
) == -1)
297 CLog::Log(LOGWARNING
, "CAESinkOSS::Initialize - Failed to get the channel order, assuming CHNORDER_NORMAL");
302 int tmp
= (CAEUtil::DataFormatToBits(format
.m_dataFormat
) >> 3) * format
.m_channelLayout
.Count() * OSS_FRAMES
;
304 while ((tmp
& 0x1) == 0x0)
310 int oss_frag
= (4 << 16) | pos
;
311 if (ioctl(m_fd
, SNDCTL_DSP_SETFRAGMENT
, &oss_frag
) == -1)
312 CLog::Log(LOGWARNING
, "CAESinkOSS::Initialize - Failed to set the fragment size");
314 int oss_sr
= format
.m_sampleRate
;
315 if (ioctl(m_fd
, SNDCTL_DSP_SPEED
, &oss_sr
) == -1)
318 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to set the sample rate");
323 if (ioctl(m_fd
, SNDCTL_DSP_GETOSPACE
, &bi
) == -1)
326 CLog::Log(LOGERROR
, "CAESinkOSS::Initialize - Failed to get the output buffer size");
330 format
.m_sampleRate
= oss_sr
;
331 format
.m_frameSize
= (CAEUtil::DataFormatToBits(format
.m_dataFormat
) >> 3) * format
.m_channelLayout
.Count();
332 format
.m_frames
= bi
.fragsize
/ format
.m_frameSize
;
339 void CAESinkOSS::Deinitialize()
347 inline CAEChannelInfo
CAESinkOSS::GetChannelLayout(const AEAudioFormat
& format
)
349 unsigned int count
= 0;
351 if (format
.m_dataFormat
== AE_FMT_RAW
)
353 switch (format
.m_streamInfo
.m_type
)
355 case CAEStreamInfo::STREAM_TYPE_DTSHD_MA
:
356 case CAEStreamInfo::STREAM_TYPE_TRUEHD
:
359 case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE
:
360 case CAEStreamInfo::STREAM_TYPE_DTS_512
:
361 case CAEStreamInfo::STREAM_TYPE_DTS_1024
:
362 case CAEStreamInfo::STREAM_TYPE_DTS_2048
:
363 case CAEStreamInfo::STREAM_TYPE_AC3
:
364 case CAEStreamInfo::STREAM_TYPE_EAC3
:
365 case CAEStreamInfo::STREAM_TYPE_DTSHD
:
375 for (unsigned int c
= 0; c
< 8; ++c
)
376 for (unsigned int i
= 0; i
< format
.m_channelLayout
.Count(); ++i
)
377 if (format
.m_channelLayout
[i
] == OSSChannelMap
[c
])
385 for (unsigned int i
= 0; i
< count
; ++i
)
386 info
+= OSSChannelMap
[i
];
391 void CAESinkOSS::Stop()
393 #ifdef SNDCTL_DSP_RESET
395 ioctl(m_fd
, SNDCTL_DSP_RESET
, NULL
);
399 void CAESinkOSS::GetDelay(AEDelayStatus
& status
)
408 if (ioctl(m_fd
, SNDCTL_DSP_GETODELAY
, &delay
) == -1)
414 status
.SetDelay((double)delay
/ (m_format
.m_frameSize
* m_format
.m_sampleRate
));
417 unsigned int CAESinkOSS::AddPackets(uint8_t **data
, unsigned int frames
, unsigned int offset
)
419 int size
= frames
* m_format
.m_frameSize
;
422 CLog::Log(LOGERROR
, "CAESinkOSS::AddPackets - Failed to write");
426 void *buffer
= data
[0]+offset
*m_format
.m_frameSize
;
427 int wrote
= write(m_fd
, buffer
, size
);
430 CLog::Log(LOGERROR
, "CAESinkOSS::AddPackets - Failed to write");
434 return wrote
/ m_format
.m_frameSize
;
437 void CAESinkOSS::Drain()
442 if(ioctl(m_fd
, SNDCTL_DSP_SYNC
, NULL
) == -1)
444 CLog::Log(LOGERROR
, "CAESinkOSS::Drain - Draining the Sink failed");
448 void CAESinkOSS::EnumerateDevicesEx(AEDeviceInfoList
&list
, bool force
)
451 const char * mixerdev
= "/dev/mixer";
453 if ((mixerfd
= open(mixerdev
, O_RDWR
, 0)) == -1)
455 CLog::Log(LOGINFO
, "CAESinkOSS::EnumerateDevicesEx - No OSS mixer device present: {}",
460 #if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
462 if (ioctl(mixerfd
, SNDCTL_SYSINFO
, &sysinfo
) == -1)
464 // hardware not supported
470 for (int i
= 0; i
< sysinfo
.numcards
; ++i
)
472 std::stringstream devicepath
;
473 std::stringstream devicename
;
475 oss_card_info cardinfo
;
477 devicepath
<< "/dev/dsp" << i
;
478 info
.m_deviceName
= devicepath
.str();
481 if (ioctl(mixerfd
, SNDCTL_CARDINFO
, &cardinfo
) == -1)
484 devicename
<< cardinfo
.shortname
<< " " << cardinfo
.longname
;
485 info
.m_displayName
= devicename
.str();
487 info
.m_dataFormats
.push_back(AE_FMT_S16NE
);
488 info
.m_dataFormats
.push_back(AE_FMT_S32NE
);
489 if (info
.m_displayName
.find("HDMI") != std::string::npos
490 || info
.m_displayName
.find("DisplayPort") != std::string::npos
)
492 info
.m_deviceType
= AE_DEVTYPE_HDMI
;
493 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_AC3
);
494 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE
);
495 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024
);
496 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048
);
497 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512
);
498 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_EAC3
);
499 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD
);
500 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA
);
501 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD
);
502 info
.m_dataFormats
.push_back(AE_FMT_RAW
);
504 else if (info
.m_displayName
.find("Digital") != std::string::npos
)
506 info
.m_deviceType
= AE_DEVTYPE_IEC958
;
507 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_AC3
);
508 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE
);
509 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024
);
510 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048
);
511 info
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512
);
512 info
.m_dataFormats
.push_back(AE_FMT_RAW
);
516 info
.m_deviceType
= AE_DEVTYPE_PCM
;
519 oss_audioinfo ainfo
= {};
521 if (ioctl(mixerfd
, SNDCTL_AUDIOINFO
, &ainfo
) != -1) {
523 if (ainfo
.oformats
& AFMT_S32_LE
)
524 info
.m_dataFormats
.push_back(AE_FMT_S32LE
);
525 if (ainfo
.oformats
& AFMT_S16_LE
)
526 info
.m_dataFormats
.push_back(AE_FMT_S16LE
);
529 j
< ainfo
.max_channels
&& AE_CH_NULL
!= OSSChannelMap
[j
];
531 info
.m_channels
+= OSSChannelMap
[j
];
533 for (int *rate
= OSSSampleRateList
; *rate
!= 0; ++rate
)
534 if (*rate
>= ainfo
.min_rate
&& *rate
<= ainfo
.max_rate
)
535 info
.m_sampleRates
.push_back(*rate
);
537 info
.m_wantsIECPassthrough
= true;
538 list
.push_back(info
);