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