[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkOSS.cpp
blob5cb864a80eb4f5faae8b98327cd5078fdea8952f
1 /*
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.
7 */
9 #include "AESinkOSS.h"
10 #include <stdint.h>
11 #include <limits.h>
12 #include <unistd.h>
14 #include "cores/AudioEngine/AESinkFactory.h"
15 #include "cores/AudioEngine/Utils/AEUtil.h"
16 #include "utils/log.h"
17 #include "threads/SingleLock.h"
18 #include <sstream>
20 #include <sys/ioctl.h>
21 #include <sys/fcntl.h>
23 #if defined(OSS4) || defined(TARGET_FREEBSD)
24 #include <sys/soundcard.h>
25 #else
26 #include <linux/soundcard.h>
27 #endif
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[] =
37 5512,
38 8000,
39 11025,
40 16000,
41 22050,
42 32000,
43 44100,
44 48000,
45 64000,
46 88200,
47 96000,
48 176400,
49 192000,
50 384000,
53 #endif
55 CAESinkOSS::CAESinkOSS()
57 m_fd = 0;
60 CAESinkOSS::~CAESinkOSS()
62 Deinitialize();
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))
78 return sink;
80 return {};
83 std::string CAESinkOSS::GetDeviceUse(const AEAudioFormat& format, const std::string &device)
85 #ifdef OSS4
86 if (AE_IS_RAW(format.m_dataFormat))
88 if (device.find_first_of('/') != 0)
89 return "/dev/dsp_ac3";
90 return device;
93 if (device.find_first_of('/') != 0)
94 return "/dev/dsp_multich";
95 #else
96 if (device.find_first_of('/') != 0)
97 return "/dev/dsp";
98 #endif
100 return device;
103 bool CAESinkOSS::Initialize(AEAudioFormat &format, std::string &device)
105 m_initFormat = format;
106 format.m_channelLayout = GetChannelLayout(format);
107 device = GetDeviceUse(format, device);
109 #ifdef __linux__
110 /* try to open in exclusive mode first (no software mixing) */
111 m_fd = open(device.c_str(), O_WRONLY | O_EXCL, 0);
112 if (m_fd == -1)
113 #endif
114 m_fd = open(device.c_str(), O_WRONLY, 0);
115 if (m_fd == -1)
117 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to open the audio device: {}", device);
118 return false;
121 int format_mask;
122 if (ioctl(m_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1)
124 close(m_fd);
125 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get supported formats, assuming AFMT_S16_NE");
126 return false;
129 #ifdef OSS4
130 bool useCooked = true;
131 #endif
133 int oss_fmt = 0;
134 #ifdef AFMT_FLOAT
135 if ((format.m_dataFormat == AE_FMT_FLOAT) && (format_mask & AFMT_FLOAT ))
136 oss_fmt = AFMT_FLOAT;
137 else
138 #endif
139 #ifdef AFMT_S32_NE
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;
146 else
147 #endif
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 ))
155 oss_fmt = AFMT_U8;
156 else if (((format.m_dataFormat == AE_FMT_RAW) ) && (format_mask & AFMT_AC3 ))
158 oss_fmt = AFMT_AC3;
159 format.m_dataFormat = AE_FMT_S16NE;
161 else if (format.m_dataFormat == AE_FMT_RAW)
163 close(m_fd);
164 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to find a suitable RAW output format");
165 return false;
167 else
169 CLog::Log(LOGINFO,
170 "CAESinkOSS::Initialize - Your hardware does not support {}, trying other formats",
171 CAEUtil::DataFormatToStr(format.m_dataFormat));
173 /* fallback to the best supported format */
174 #ifdef AFMT_FLOAT
175 if (format_mask & AFMT_FLOAT )
177 oss_fmt = AFMT_FLOAT;
178 format.m_dataFormat = AE_FMT_FLOAT;
180 else
181 #endif
182 #ifdef AFMT_S32_NE
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;
198 else
199 #endif
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 )
217 oss_fmt = AFMT_U8;
218 format.m_dataFormat = AE_FMT_U8;
220 else
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;
225 #ifdef OSS4
226 /* dont use cooked if we did not find a native format, OSS might be able to convert */
227 useCooked = false;
228 #endif
232 #ifdef OSS4
233 if (useCooked)
235 int oss_cooked = 1;
236 if (ioctl(m_fd, SNDCTL_DSP_COOKEDMODE, &oss_cooked) == -1)
237 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set cooked mode");
239 #endif
241 if (ioctl(m_fd, SNDCTL_DSP_SETFMT, &oss_fmt) == -1)
243 close(m_fd);
244 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the data format ({})",
245 CAEUtil::DataFormatToStr(format.m_dataFormat));
246 return false;
249 /* find the number we need to open to access the channels we need */
250 bool found = false;
251 int oss_ch = 0;
252 for (int ch = format.m_channelLayout.Count(); ch < 9; ++ch)
254 oss_ch = ch;
255 if (ioctl(m_fd, SNDCTL_DSP_CHANNELS, &oss_ch) != -1 && oss_ch >= (int)format.m_channelLayout.Count())
257 found = true;
258 break;
262 if (!found)
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");
274 #elif defined(OSS4)
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;
289 default:
290 continue;
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");
300 #endif
302 int tmp = (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3) * format.m_channelLayout.Count() * OSS_FRAMES;
303 int pos = 0;
304 while ((tmp & 0x1) == 0x0)
306 tmp = tmp >> 1;
307 ++pos;
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)
317 close(m_fd);
318 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the sample rate");
319 return false;
322 audio_buf_info bi;
323 if (ioctl(m_fd, SNDCTL_DSP_GETOSPACE, &bi) == -1)
325 close(m_fd);
326 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get the output buffer size");
327 return false;
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;
334 m_device = device;
335 m_format = format;
336 return true;
339 void CAESinkOSS::Deinitialize()
341 Stop();
343 if (m_fd != -1)
344 close(m_fd);
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:
357 count = 8;
358 break;
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:
366 count = 2;
367 break;
368 default:
369 count = 0;
370 break;
373 else
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])
379 count = c + 1;
380 break;
384 CAEChannelInfo info;
385 for (unsigned int i = 0; i < count; ++i)
386 info += OSSChannelMap[i];
388 return info;
391 void CAESinkOSS::Stop()
393 #ifdef SNDCTL_DSP_RESET
394 if (m_fd != -1)
395 ioctl(m_fd, SNDCTL_DSP_RESET, NULL);
396 #endif
399 void CAESinkOSS::GetDelay(AEDelayStatus& status)
401 if (m_fd == -1)
403 status.SetDelay(0);
404 return;
407 int delay;
408 if (ioctl(m_fd, SNDCTL_DSP_GETODELAY, &delay) == -1)
410 status.SetDelay(0);
411 return;
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;
420 if (m_fd == -1)
422 CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
423 return INT_MAX;
426 void *buffer = data[0]+offset*m_format.m_frameSize;
427 int wrote = write(m_fd, buffer, size);
428 if (wrote < 0)
430 CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
431 return INT_MAX;
434 return wrote / m_format.m_frameSize;
437 void CAESinkOSS::Drain()
439 if (m_fd == -1)
440 return;
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)
450 int mixerfd;
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: {}",
456 mixerdev);
457 return;
460 #if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
461 oss_sysinfo sysinfo;
462 if (ioctl(mixerfd, SNDCTL_SYSINFO, &sysinfo) == -1)
464 // hardware not supported
465 // OSSv4 required ?
466 close(mixerfd);
467 return;
470 for (int i = 0; i < sysinfo.numcards; ++i)
472 std::stringstream devicepath;
473 std::stringstream devicename;
474 CAEDeviceInfo info;
475 oss_card_info cardinfo;
477 devicepath << "/dev/dsp" << i;
478 info.m_deviceName = devicepath.str();
480 cardinfo.card = i;
481 if (ioctl(mixerfd, SNDCTL_CARDINFO, &cardinfo) == -1)
482 break;
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);
514 else
516 info.m_deviceType = AE_DEVTYPE_PCM;
519 oss_audioinfo ainfo = {};
520 ainfo.dev = i;
521 if (ioctl(mixerfd, SNDCTL_AUDIOINFO, &ainfo) != -1) {
522 #if 0
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);
527 #endif
528 for (int j = 0;
529 j < ainfo.max_channels && AE_CH_NULL != OSSChannelMap[j];
530 ++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);
540 #endif
541 close(mixerfd);