output/oss: enable DoP
Explanation
This adds support for DOP using the PcmExport function if the macro
ENABLE_DSD is defined. If enabled within the config-file using "dop",
the boolean dop_setting will be true. If DSD input is encountered and
the setting is on, it is checked whether the oss-device supports the
required samplerate. If that is the case, dop_active is set to true
and conversion of the input is prevented. If the sample rate is not
supported, conversion to S32 is requested. When playing back, the
PcmExport is used to pack the incoming stream into PCM.  Reasoning
This is required for OSs without the required driver support for
native DSD playback that also have no ALSA. Mainly *BSD users are the
target audience for this functionality, as ALSA here is only a proxy
without full functionality.  Requirements
    DAC that supports the DOP standard
    Building with OSS, DSD and S32-Format
Supported Formats / Required PCM Formats
DSF, DFF and WavPack-DSD will work.
DSD64, 1 Channel -> S24:176.4kHz (untested, lack of time / missing samples)
DSD64, 2 Channel -> S24:352.8kHz
DSD64, 4 Channel -> S24:705.6kHz (untested, lmissing equipment)
DSD128, 1 Channel -> S24:352.8kHz (untested, lack of time / missing samples)
DSD128, 2 Channel -> S24:705.6kHz
DSD256, 1 Channel -> S24:705.6kHz (untested, lack of time / missing samples)
Changes
    inclusion of required files
    adding new domain for logging
    adding dop_satisfied private function
    adding required member variables for storing dop state and for dop-packing
    adding dop boolean parameter to many functions that are required to act a little differently when dop is active
Testing
This has been tested to work with a Sabaj Da2 on FreeBSD, where the
red status indicator LED clearly shows that DSD playback is taking
place, instead of purple for "hi-res" which is seen when converting.
Issues
I have not tested this with S24 and right now AFMT_S32_NE is
required. If not defined, ENABLE_DSD will be undef'ed. This will be
addressed in a bit, however no DAC which supports DOP but not 32Bit is
known to me. Also, AFMT_S32_NE is not defined when building on FreeBSD
which is why this is just blatantly defined in the file at the moment.
Additionally, the new dop-option is not added into any documentation
whatsoever.
			
			
This commit is contained in:
		 Constantin Fuerst
					Constantin Fuerst
				
			
				
					committed by
					
						 Max Kellermann
						Max Kellermann
					
				
			
			
				
	
			
			
			 Max Kellermann
						Max Kellermann
					
				
			
						parent
						
							dee5d1b87b
						
					
				
				
					commit
					955502f881
				
			
							
								
								
									
										1
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								NEWS
									
									
									
									
									
								
							| @@ -10,6 +10,7 @@ ver 0.23 (not yet released) | |||||||
| * decoder | * decoder | ||||||
|   - openmpt: new plugin |   - openmpt: new plugin | ||||||
| * output | * output | ||||||
|  |   - oss: support DSD over PCM | ||||||
|   - pipewire: new plugin |   - pipewire: new plugin | ||||||
|   - snapcast: new plugin |   - snapcast: new plugin | ||||||
| * tags | * tags | ||||||
|   | |||||||
| @@ -1004,6 +1004,8 @@ On Linux, OSS has been superseded by ALSA. Use the ALSA output plugin :ref:`alsa | |||||||
|      - Description |      - Description | ||||||
|    * - **device PATH** |    * - **device PATH** | ||||||
|      - Sets the path of the PCM device. If not specified, then MPD will attempt to open /dev/sound/dsp and /dev/dsp. |      - Sets the path of the PCM device. If not specified, then MPD will attempt to open /dev/sound/dsp and /dev/dsp. | ||||||
|  |    * - **dop yes|no** | ||||||
|  |      - If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk. | ||||||
|  |  | ||||||
| The according hardware mixer plugin understands the following settings: | The according hardware mixer plugin understands the following settings: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -55,9 +55,24 @@ | |||||||
| #undef AFMT_S24_NE | #undef AFMT_S24_NE | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #if defined(ENABLE_DSD) && defined(AFMT_S32_NE) | ||||||
|  | #define ENABLE_OSS_DSD | ||||||
|  | #endif | ||||||
|  |  | ||||||
| class OssOutput final : AudioOutput { | class OssOutput final : AudioOutput { | ||||||
| 	Manual<PcmExport> pcm_export; | 	Manual<PcmExport> pcm_export; | ||||||
|  |  | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 	/** | ||||||
|  | 	 * Enable DSD over PCM according to the DoP standard? | ||||||
|  | 	 * | ||||||
|  | 	 * @see http://dsd-guide.com/dop-open-standard | ||||||
|  | 	 * | ||||||
|  | 	 * this is default in oss as no other dsd-method is known to man | ||||||
|  | 	 */ | ||||||
|  | 	const bool dop_setting; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 	FileDescriptor fd = FileDescriptor::Undefined(); | 	FileDescriptor fd = FileDescriptor::Undefined(); | ||||||
| 	const char *device; | 	const char *device; | ||||||
|  |  | ||||||
| @@ -70,9 +85,18 @@ class OssOutput final : AudioOutput { | |||||||
| 	static constexpr unsigned oss_flags = FLAG_ENABLE_DISABLE; | 	static constexpr unsigned oss_flags = FLAG_ENABLE_DISABLE; | ||||||
|  |  | ||||||
| public: | public: | ||||||
| 	explicit OssOutput(const char *_device=nullptr) | 	explicit OssOutput(const char *_device=nullptr | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 			   , bool dop = false | ||||||
|  | #endif | ||||||
|  | 			   ) | ||||||
| 		:AudioOutput(oss_flags), | 		:AudioOutput(oss_flags), | ||||||
| 		 device(_device) {} | #ifdef ENABLE_OSS_DSD | ||||||
|  | 		 dop_setting(dop), | ||||||
|  | #endif | ||||||
|  | 		 device(_device) | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	static AudioOutput *Create(EventLoop &event_loop, | 	static AudioOutput *Create(EventLoop &event_loop, | ||||||
| 				   const ConfigBlock &block); | 				   const ConfigBlock &block); | ||||||
| @@ -100,6 +124,12 @@ private: | |||||||
| 	 */ | 	 */ | ||||||
| 	void Setup(AudioFormat &audio_format); | 	void Setup(AudioFormat &audio_format); | ||||||
|  |  | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 	void SetupDop(const AudioFormat &audio_format); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | 	void SetupOrDop(AudioFormat &audio_format); | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Reopen the device with the saved audio_format, without any probing. | 	 * Reopen the device with the saved audio_format, without any probing. | ||||||
| 	 * | 	 * | ||||||
| @@ -165,7 +195,11 @@ oss_output_test_default_device() noexcept | |||||||
| } | } | ||||||
|  |  | ||||||
| static OssOutput * | static OssOutput * | ||||||
| oss_open_default() | oss_open_default( | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 		 bool dop | ||||||
|  | #endif | ||||||
|  | 		 ) | ||||||
| { | { | ||||||
| 	int err[std::size(default_devices)]; | 	int err[std::size(default_devices)]; | ||||||
| 	enum oss_stat ret[std::size(default_devices)]; | 	enum oss_stat ret[std::size(default_devices)]; | ||||||
| @@ -173,7 +207,11 @@ oss_open_default() | |||||||
| 	for (int i = std::size(default_devices); --i >= 0; ) { | 	for (int i = std::size(default_devices); --i >= 0; ) { | ||||||
| 		ret[i] = oss_stat_device(default_devices[i], &err[i]); | 		ret[i] = oss_stat_device(default_devices[i], &err[i]); | ||||||
| 		if (ret[i] == OSS_STAT_NO_ERROR) | 		if (ret[i] == OSS_STAT_NO_ERROR) | ||||||
| 			return new OssOutput(default_devices[i]); | 			return new OssOutput(default_devices[i] | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 					     , dop | ||||||
|  | #endif | ||||||
|  | 					     ); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for (int i = std::size(default_devices); --i >= 0; ) { | 	for (int i = std::size(default_devices); --i >= 0; ) { | ||||||
| @@ -206,11 +244,23 @@ oss_open_default() | |||||||
| AudioOutput * | AudioOutput * | ||||||
| OssOutput::Create(EventLoop &, const ConfigBlock &block) | OssOutput::Create(EventLoop &, const ConfigBlock &block) | ||||||
| { | { | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 	bool dop = block.GetBlockValue("dop", false); | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 	const char *device = block.GetBlockValue("device"); | 	const char *device = block.GetBlockValue("device"); | ||||||
| 	if (device != nullptr) | 	if (device != nullptr) | ||||||
| 		return new OssOutput(device); | 		return new OssOutput(device | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 				     , dop | ||||||
|  | #endif | ||||||
|  | 				     ); | ||||||
|  |  | ||||||
| 	return oss_open_default(); | 	return oss_open_default( | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 				dop | ||||||
|  | #endif | ||||||
|  | 				); | ||||||
| } | } | ||||||
|  |  | ||||||
| void | void | ||||||
| @@ -450,6 +500,7 @@ oss_probe_sample_format(FileDescriptor fd, SampleFormat sample_format, | |||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	sample_format = sample_format_from_oss(oss_format); | 	sample_format = sample_format_from_oss(oss_format); | ||||||
|  |  | ||||||
| 	if (sample_format == SampleFormat::UNDEFINED) | 	if (sample_format == SampleFormat::UNDEFINED) | ||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| @@ -523,6 +574,67 @@ OssOutput::Setup(AudioFormat &_audio_format) | |||||||
| 				pcm_export); | 				pcm_export); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  |  | ||||||
|  | void | ||||||
|  | OssOutput::SetupDop(const AudioFormat &audio_format) | ||||||
|  | { | ||||||
|  | 	assert(audio_format.format == SampleFormat::DSD); | ||||||
|  |  | ||||||
|  | 	effective_channels = audio_format.channels; | ||||||
|  |  | ||||||
|  | 	/* DoP packs two 8-bit "samples" in one 24-bit "sample" */ | ||||||
|  | 	effective_speed = audio_format.sample_rate / 2; | ||||||
|  |  | ||||||
|  | 	effective_samplesize = AFMT_S32_NE; | ||||||
|  |  | ||||||
|  | 	OssIoctlExact(fd, SNDCTL_DSP_CHANNELS, effective_channels, | ||||||
|  | 		      "Failed to set channel count"); | ||||||
|  | 	OssIoctlExact(fd, SNDCTL_DSP_SPEED, effective_speed, | ||||||
|  | 		      "Failed to set sample rate"); | ||||||
|  | 	OssIoctlExact(fd, SNDCTL_DSP_SAMPLESIZE, effective_samplesize, | ||||||
|  | 		      "Failed to set sample format"); | ||||||
|  |  | ||||||
|  | 	PcmExport::Params params; | ||||||
|  | 	params.alsa_channel_order = true; | ||||||
|  | 	params.dsd_mode = PcmExport::DsdMode::DOP; | ||||||
|  | 	params.shift8 = true; | ||||||
|  |  | ||||||
|  | 	pcm_export->Open(audio_format.format, audio_format.channels, params); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | void | ||||||
|  | OssOutput::SetupOrDop(AudioFormat &audio_format) | ||||||
|  | { | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 	std::exception_ptr dop_error; | ||||||
|  | 	if (dop_setting && audio_format.format == SampleFormat::DSD) { | ||||||
|  | 		try { | ||||||
|  | 			SetupDop(audio_format); | ||||||
|  | 			return; | ||||||
|  | 		} catch (...) { | ||||||
|  | 			dop_error = std::current_exception(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	try { | ||||||
|  | #endif | ||||||
|  | 		Setup(audio_format); | ||||||
|  | #ifdef ENABLE_OSS_DSD | ||||||
|  | 	} catch (...) { | ||||||
|  | 		if (dop_error) | ||||||
|  | 			/* if DoP was attempted, prefer returning the | ||||||
|  | 			   original DoP error instead of the fallback | ||||||
|  | 			   error */ | ||||||
|  | 			std::rethrow_exception(dop_error); | ||||||
|  | 		else | ||||||
|  | 			throw; | ||||||
|  | 	} | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Reopen the device with the saved audio_format, without any probing. |  * Reopen the device with the saved audio_format, without any probing. | ||||||
|  */ |  */ | ||||||
| @@ -551,7 +663,7 @@ try { | |||||||
| 	if (!fd.Open(device, O_WRONLY)) | 	if (!fd.Open(device, O_WRONLY)) | ||||||
| 		throw FormatErrno("Error opening OSS device \"%s\"", device); | 		throw FormatErrno("Error opening OSS device \"%s\"", device); | ||||||
|  |  | ||||||
| 	Setup(_audio_format); | 	SetupOrDop(_audio_format); | ||||||
| } catch (...) { | } catch (...) { | ||||||
| 	DoClose(); | 	DoClose(); | ||||||
| 	throw; | 	throw; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user