1 /*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "AESinkOSS.h"
10 #include <stdint.h>
11 #include <limits.h>
12 #include <unistd.h>
13
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>
19
20 #include <sys/ioctl.h>
21 #include <sys/fcntl.h>
22
23 #if defined(OSS4) || (defined(TARGET_FREEBSD)||defined(TARGET_DRAGONFLY))
24 #include <sys/soundcard.h>
25 #else
26 #include <linux/soundcard.h>
27 #endif
28
29 #define OSS_FRAMES 256
30
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};
33
34 #if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
35 static int OSSSampleRateList[] =
36 {
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,
51 0
52 };
53 #endif
54
CAESinkOSS()55 CAESinkOSS::CAESinkOSS()
56 {
57 m_fd = 0;
58 }
59
~CAESinkOSS()60 CAESinkOSS::~CAESinkOSS()
61 {
62 Deinitialize();
63 }
64
Register()65 void CAESinkOSS::Register()
66 {
67 AE::AESinkRegEntry entry;
68 entry.sinkName = "OSS";
69 entry.createFunc = CAESinkOSS::Create;
70 entry.enumerateFunc = CAESinkOSS::EnumerateDevicesEx;
71 AE::CAESinkFactory::RegisterSink(entry);
72 }
73
Create(std::string & device,AEAudioFormat & desiredFormat)74 IAESink* CAESinkOSS::Create(std::string &device, AEAudioFormat& desiredFormat)
75 {
76 IAESink* sink = new CAESinkOSS();
77 if (sink->Initialize(desiredFormat, device))
78 return sink;
79
80 delete sink;
81 return nullptr;
82 }
83
GetDeviceUse(const AEAudioFormat & format,const std::string & device)84 std::string CAESinkOSS::GetDeviceUse(const AEAudioFormat& format, const std::string &device)
85 {
86 #ifdef OSS4
87 if (AE_IS_RAW(format.m_dataFormat))
88 {
89 if (device.find_first_of('/') != 0)
90 return "/dev/dsp_ac3";
91 return device;
92 }
93
94 if (device.find_first_of('/') != 0)
95 return "/dev/dsp_multich";
96 #else
97 if (device.find_first_of('/') != 0)
98 return "/dev/dsp";
99 #endif
100
101 return device;
102 }
103
Initialize(AEAudioFormat & format,std::string & device)104 bool CAESinkOSS::Initialize(AEAudioFormat &format, std::string &device)
105 {
106 m_initFormat = format;
107 format.m_channelLayout = GetChannelLayout(format);
108 device = GetDeviceUse(format, device);
109
110 #ifdef __linux__
111 /* try to open in exclusive mode first (no software mixing) */
112 m_fd = open(device.c_str(), O_WRONLY | O_EXCL, 0);
113 if (m_fd == -1)
114 #endif
115 m_fd = open(device.c_str(), O_WRONLY, 0);
116 if (m_fd == -1)
117 {
118 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to open the audio device: %s", device.c_str());
119 return false;
120 }
121
122 int format_mask;
123 if (ioctl(m_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1)
124 {
125 close(m_fd);
126 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get supported formats, assuming AFMT_S16_NE");
127 return false;
128 }
129
130 #ifdef OSS4
131 bool useCooked = true;
132 #endif
133
134 int oss_fmt = 0;
135 #ifdef AFMT_FLOAT
136 if ((format.m_dataFormat == AE_FMT_FLOAT) && (format_mask & AFMT_FLOAT ))
137 oss_fmt = AFMT_FLOAT;
138 else
139 #endif
140 #ifdef AFMT_S32_NE
141 if ((format.m_dataFormat == AE_FMT_S32NE) && (format_mask & AFMT_S32_NE))
142 oss_fmt = AFMT_S32_NE;
143 else if ((format.m_dataFormat == AE_FMT_S32BE) && (format_mask & AFMT_S32_BE))
144 oss_fmt = AFMT_S32_BE;
145 else if ((format.m_dataFormat == AE_FMT_S32LE) && (format_mask & AFMT_S32_LE))
146 oss_fmt = AFMT_S32_LE;
147 else
148 #endif
149 if ((format.m_dataFormat == AE_FMT_S16NE) && (format_mask & AFMT_S16_NE))
150 oss_fmt = AFMT_S16_NE;
151 else if ((format.m_dataFormat == AE_FMT_S16BE) && (format_mask & AFMT_S16_BE))
152 oss_fmt = AFMT_S16_BE;
153 else if ((format.m_dataFormat == AE_FMT_S16LE) && (format_mask & AFMT_S16_LE))
154 oss_fmt = AFMT_S16_LE;
155 else if ((format.m_dataFormat == AE_FMT_U8 ) && (format_mask & AFMT_U8 ))
156 oss_fmt = AFMT_U8;
157 else if (((format.m_dataFormat == AE_FMT_RAW) ) && (format_mask & AFMT_AC3 ))
158 {
159 oss_fmt = AFMT_AC3;
160 format.m_dataFormat = AE_FMT_S16NE;
161 }
162 else if (format.m_dataFormat == AE_FMT_RAW)
163 {
164 close(m_fd);
165 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to find a suitable RAW output format");
166 return false;
167 }
168 else
169 {
170 CLog::Log(LOGINFO, "CAESinkOSS::Initialize - Your hardware does not support %s, trying other formats", CAEUtil::DataFormatToStr(format.m_dataFormat));
171
172 /* fallback to the best supported format */
173 #ifdef AFMT_FLOAT
174 if (format_mask & AFMT_FLOAT )
175 {
176 oss_fmt = AFMT_FLOAT;
177 format.m_dataFormat = AE_FMT_FLOAT;
178 }
179 else
180 #endif
181 #ifdef AFMT_S32_NE
182 if (format_mask & AFMT_S32_NE)
183 {
184 oss_fmt = AFMT_S32_NE;
185 format.m_dataFormat = AE_FMT_S32NE;
186 }
187 else if (format_mask & AFMT_S32_BE)
188 {
189 oss_fmt = AFMT_S32_BE;
190 format.m_dataFormat = AE_FMT_S32BE;
191 }
192 else if (format_mask & AFMT_S32_LE)
193 {
194 oss_fmt = AFMT_S32_LE;
195 format.m_dataFormat = AE_FMT_S32LE;
196 }
197 else
198 #endif
199 if (format_mask & AFMT_S16_NE)
200 {
201 oss_fmt = AFMT_S16_NE;
202 format.m_dataFormat = AE_FMT_S16NE;
203 }
204 else if (format_mask & AFMT_S16_BE)
205 {
206 oss_fmt = AFMT_S16_BE;
207 format.m_dataFormat = AE_FMT_S16BE;
208 }
209 else if (format_mask & AFMT_S16_LE)
210 {
211 oss_fmt = AFMT_S16_LE;
212 format.m_dataFormat = AE_FMT_S16LE;
213 }
214 else if (format_mask & AFMT_U8 )
215 {
216 oss_fmt = AFMT_U8;
217 format.m_dataFormat = AE_FMT_U8;
218 }
219 else
220 {
221 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to find a suitable native output format, will try to use AE_FMT_S16NE anyway");
222 oss_fmt = AFMT_S16_NE;
223 format.m_dataFormat = AE_FMT_S16NE;
224 #ifdef OSS4
225 /* dont use cooked if we did not find a native format, OSS might be able to convert */
226 useCooked = false;
227 #endif
228 }
229 }
230
231 #ifdef OSS4
232 if (useCooked)
233 {
234 int oss_cooked = 1;
235 if (ioctl(m_fd, SNDCTL_DSP_COOKEDMODE, &oss_cooked) == -1)
236 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set cooked mode");
237 }
238 #endif
239
240 if (ioctl(m_fd, SNDCTL_DSP_SETFMT, &oss_fmt) == -1)
241 {
242 close(m_fd);
243 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the data format (%s)", CAEUtil::DataFormatToStr(format.m_dataFormat));
244 return false;
245 }
246
247 /* find the number we need to open to access the channels we need */
248 bool found = false;
249 int oss_ch = 0;
250 for (int ch = format.m_channelLayout.Count(); ch < 9; ++ch)
251 {
252 oss_ch = ch;
253 if (ioctl(m_fd, SNDCTL_DSP_CHANNELS, &oss_ch) != -1 && oss_ch >= (int)format.m_channelLayout.Count())
254 {
255 found = true;
256 break;
257 }
258 }
259
260 if (!found)
261 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to access the number of channels required, falling back");
262
263 #if (defined(TARGET_FREEBSD)||defined(TARGET_DRAGONFLY))
264 /* fix hdmi 8 channels order */
265 if ((oss_fmt != AFMT_AC3) && 8 == oss_ch)
266 {
267 unsigned long long order = 0x0000000087346521ULL;
268
269 if (ioctl(m_fd, SNDCTL_DSP_SET_CHNORDER, &order) == -1)
270 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set the channel order");
271 }
272 #elif defined(OSS4)
273 unsigned long long order = 0;
274
275 for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
276 switch (format.m_channelLayout[i])
277 {
278 case AE_CH_FL : order = (order << 4) | CHID_L ; break;
279 case AE_CH_FR : order = (order << 4) | CHID_R ; break;
280 case AE_CH_FC : order = (order << 4) | CHID_C ; break;
281 case AE_CH_LFE: order = (order << 4) | CHID_LFE; break;
282 case AE_CH_SL : order = (order << 4) | CHID_LS ; break;
283 case AE_CH_SR : order = (order << 4) | CHID_RS ; break;
284 case AE_CH_BL : order = (order << 4) | CHID_LR ; break;
285 case AE_CH_BR : order = (order << 4) | CHID_RR ; break;
286
287 default:
288 continue;
289 }
290
291 if (ioctl(m_fd, SNDCTL_DSP_SET_CHNORDER, &order) == -1)
292 {
293 if (ioctl(m_fd, SNDCTL_DSP_GET_CHNORDER, &order) == -1)
294 {
295 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to get the channel order, assuming CHNORDER_NORMAL");
296 }
297 }
298 #endif
299
300 int tmp = (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3) * format.m_channelLayout.Count() * OSS_FRAMES;
301 int pos = 0;
302 while ((tmp & 0x1) == 0x0)
303 {
304 tmp = tmp >> 1;
305 ++pos;
306 }
307
308 int oss_frag = (4 << 16) | pos;
309 if (ioctl(m_fd, SNDCTL_DSP_SETFRAGMENT, &oss_frag) == -1)
310 CLog::Log(LOGWARNING, "CAESinkOSS::Initialize - Failed to set the fragment size");
311
312 int oss_sr = format.m_sampleRate;
313 if (ioctl(m_fd, SNDCTL_DSP_SPEED, &oss_sr) == -1)
314 {
315 close(m_fd);
316 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to set the sample rate");
317 return false;
318 }
319
320 audio_buf_info bi;
321 if (ioctl(m_fd, SNDCTL_DSP_GETOSPACE, &bi) == -1)
322 {
323 close(m_fd);
324 CLog::Log(LOGERROR, "CAESinkOSS::Initialize - Failed to get the output buffer size");
325 return false;
326 }
327
328 format.m_sampleRate = oss_sr;
329 format.m_frameSize = (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3) * format.m_channelLayout.Count();
330 format.m_frames = bi.fragsize / format.m_frameSize;
331
332 m_device = device;
333 m_format = format;
334 return true;
335 }
336
Deinitialize()337 void CAESinkOSS::Deinitialize()
338 {
339 Stop();
340
341 if (m_fd != -1)
342 close(m_fd);
343 }
344
GetChannelLayout(const AEAudioFormat & format)345 inline CAEChannelInfo CAESinkOSS::GetChannelLayout(const AEAudioFormat& format)
346 {
347 unsigned int count = 0;
348
349 if (format.m_dataFormat == AE_FMT_RAW)
350 {
351 switch (format.m_streamInfo.m_type)
352 {
353 case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
354 case CAEStreamInfo::STREAM_TYPE_TRUEHD:
355 count = 8;
356 break;
357 case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
358 case CAEStreamInfo::STREAM_TYPE_DTS_512:
359 case CAEStreamInfo::STREAM_TYPE_DTS_1024:
360 case CAEStreamInfo::STREAM_TYPE_DTS_2048:
361 case CAEStreamInfo::STREAM_TYPE_AC3:
362 case CAEStreamInfo::STREAM_TYPE_EAC3:
363 case CAEStreamInfo::STREAM_TYPE_DTSHD:
364 count = 2;
365 break;
366 default:
367 count = 0;
368 break;
369 }
370 }
371 else
372 {
373 for (unsigned int c = 0; c < 8; ++c)
374 for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
375 if (format.m_channelLayout[i] == OSSChannelMap[c])
376 {
377 count = c + 1;
378 break;
379 }
380 }
381
382 CAEChannelInfo info;
383 for (unsigned int i = 0; i < count; ++i)
384 info += OSSChannelMap[i];
385
386 return info;
387 }
388
Stop()389 void CAESinkOSS::Stop()
390 {
391 #ifdef SNDCTL_DSP_RESET
392 if (m_fd != -1)
393 ioctl(m_fd, SNDCTL_DSP_RESET, NULL);
394 #endif
395 }
396
GetDelay(AEDelayStatus & status)397 void CAESinkOSS::GetDelay(AEDelayStatus& status)
398 {
399 if (m_fd == -1)
400 {
401 status.SetDelay(0);
402 return;
403 }
404
405 int delay;
406 if (ioctl(m_fd, SNDCTL_DSP_GETODELAY, &delay) == -1)
407 {
408 status.SetDelay(0);
409 return;
410 }
411
412 status.SetDelay((double)delay / (m_format.m_frameSize * m_format.m_sampleRate));
413 }
414
AddPackets(uint8_t ** data,unsigned int frames,unsigned int offset)415 unsigned int CAESinkOSS::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
416 {
417 int size = frames * m_format.m_frameSize;
418 if (m_fd == -1)
419 {
420 CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
421 return INT_MAX;
422 }
423
424 void *buffer = data[0]+offset*m_format.m_frameSize;
425 int wrote = write(m_fd, buffer, size);
426 if (wrote < 0)
427 {
428 CLog::Log(LOGERROR, "CAESinkOSS::AddPackets - Failed to write");
429 return INT_MAX;
430 }
431
432 return wrote / m_format.m_frameSize;
433 }
434
Drain()435 void CAESinkOSS::Drain()
436 {
437 if (m_fd == -1)
438 return;
439
440 if(ioctl(m_fd, SNDCTL_DSP_SYNC, NULL) == -1)
441 {
442 CLog::Log(LOGERROR, "CAESinkOSS::Drain - Draining the Sink failed");
443 }
444 }
445
EnumerateDevicesEx(AEDeviceInfoList & list,bool force)446 void CAESinkOSS::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
447 {
448 int mixerfd;
449 const char * mixerdev = "/dev/mixer";
450
451 if ((mixerfd = open(mixerdev, O_RDWR, 0)) == -1)
452 {
453 CLog::Log(LOGINFO, "CAESinkOSS::EnumerateDevicesEx - No OSS mixer device present: %s",
454 mixerdev);
455 return;
456 }
457
458 #if defined(SNDCTL_SYSINFO) && defined(SNDCTL_CARDINFO)
459 oss_sysinfo sysinfo;
460 if (ioctl(mixerfd, SNDCTL_SYSINFO, &sysinfo) == -1)
461 {
462 // hardware not supported
463 // OSSv4 required ?
464 close(mixerfd);
465 return;
466 }
467
468 for (int i = 0; i < sysinfo.numcards; ++i)
469 {
470 std::stringstream devicepath;
471 std::stringstream devicename;
472 CAEDeviceInfo info;
473 oss_card_info cardinfo;
474
475 devicepath << "/dev/dsp" << i;
476 info.m_deviceName = devicepath.str();
477
478 cardinfo.card = i;
479 if (ioctl(mixerfd, SNDCTL_CARDINFO, &cardinfo) == -1)
480 break;
481
482 devicename << cardinfo.shortname << " " << cardinfo.longname;
483 info.m_displayName = devicename.str();
484
485 info.m_dataFormats.push_back(AE_FMT_S16NE);
486 info.m_dataFormats.push_back(AE_FMT_S32NE);
487 if (info.m_displayName.find("HDMI") != std::string::npos
488 || info.m_displayName.find("DisplayPort") != std::string::npos)
489 {
490 info.m_deviceType = AE_DEVTYPE_HDMI;
491 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
492 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
493 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
494 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
495 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
496 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
497 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
498 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
499 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
500 info.m_dataFormats.push_back(AE_FMT_RAW);
501 }
502 else if (info.m_displayName.find("Digital") != std::string::npos)
503 {
504 info.m_deviceType = AE_DEVTYPE_IEC958;
505 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
506 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
507 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
508 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
509 info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
510 info.m_dataFormats.push_back(AE_FMT_RAW);
511 }
512 else
513 {
514 info.m_deviceType = AE_DEVTYPE_PCM;
515 }
516
517 oss_audioinfo ainfo;
518 memset(&ainfo, 0, sizeof(ainfo));
519 ainfo.dev = i;
520 if (ioctl(mixerfd, SNDCTL_AUDIOINFO, &ainfo) != -1) {
521 #if 0
522 if (ainfo.oformats & AFMT_S32_LE)
523 info.m_dataFormats.push_back(AE_FMT_S32LE);
524 if (ainfo.oformats & AFMT_S16_LE)
525 info.m_dataFormats.push_back(AE_FMT_S16LE);
526 #endif
527 for (int j = 0;
528 j < ainfo.max_channels && AE_CH_NULL != OSSChannelMap[j];
529 ++j)
530 info.m_channels += OSSChannelMap[j];
531
532 for (int *rate = OSSSampleRateList; *rate != 0; ++rate)
533 if (*rate >= ainfo.min_rate && *rate <= ainfo.max_rate)
534 info.m_sampleRates.push_back(*rate);
535 }
536 info.m_wantsIECPassthrough = true;
537 list.push_back(info);
538 }
539 #endif
540 close(mixerfd);
541 }
542
543