from typing import Final
from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
-from music_assistant_models.enums import ConfigEntryType
+from music_assistant_models.enums import ConfigEntryType, ContentType
+from music_assistant_models.media_items import AudioFormat
API_SCHEMA_VERSION: Final[int] = 26
MIN_SCHEMA_VERSION: Final[int] = 24
CONF_ENTRY_SAMPLE_RATES,
CONF_ENTRY_HTTP_PROFILE_FORCED_2,
)
+
+
+DEFAULT_STREAM_HEADERS = {
+ "Server": "Music Assistant",
+ "transferMode.dlna.org": "Streaming",
+ "contentFeatures.dlna.org": "DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000", # noqa: E501
+ "Cache-Control": "no-cache",
+ "Pragma": "no-cache",
+}
+ICY_HEADERS = {
+ "icy-name": "Music Assistant",
+ "icy-description": "Music Assistant - Your personal music assistant",
+ "icy-version": "1",
+ "icy-logo": MASS_LOGO_ONLINE,
+}
+
+DEFAULT_PCM_FORMAT = AudioFormat(
+ # always prefer float32 as internal pcm format to create headroom
+ # for filters such as dsp and volume normalization
+ content_type=ContentType.PCM_F32LE,
+ sample_rate=48000,
+ bit_depth=32,
+ channels=2,
+)
CONF_OUTPUT_CHANNELS,
CONF_PUBLISH_IP,
CONF_SAMPLE_RATES,
- CONF_VOLUME_NORMALIZATION,
CONF_VOLUME_NORMALIZATION_FIXED_GAIN_RADIO,
CONF_VOLUME_NORMALIZATION_FIXED_GAIN_TRACKS,
CONF_VOLUME_NORMALIZATION_RADIO,
CONF_VOLUME_NORMALIZATION_TRACKS,
- MASS_LOGO_ONLINE,
+ DEFAULT_PCM_FORMAT,
+ DEFAULT_STREAM_HEADERS,
+ ICY_HEADERS,
SILENCE_FILE,
VERBOSE_LOG_LEVEL,
)
from music_assistant_models.streamdetails import StreamDetails
-DEFAULT_STREAM_HEADERS = {
- "Server": "Music Assistant",
- "transferMode.dlna.org": "Streaming",
- "contentFeatures.dlna.org": "DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000", # noqa: E501
- "Cache-Control": "no-cache",
- "Pragma": "no-cache",
-}
-ICY_HEADERS = {
- "icy-name": "Music Assistant",
- "icy-description": "Music Assistant - Your personal music assistant",
- "icy-version": "1",
- "icy-logo": MASS_LOGO_ONLINE,
-}
-FLOW_DEFAULT_SAMPLE_RATE = 48000
-FLOW_DEFAULT_BIT_DEPTH = 24
-
-
isfile = wrap(os.path.isfile)
)
# pick pcm format based on the streamdetails and player capabilities
- if self.mass.config.get_raw_player_config_value(queue_id, CONF_VOLUME_NORMALIZATION, True):
- # prefer f32 when volume normalization is enabled
- bit_depth = 32
- floating_point = True
- else:
- bit_depth = queue_item.streamdetails.audio_format.bit_depth
- floating_point = False
pcm_format = AudioFormat(
- content_type=ContentType.from_bit_depth(bit_depth, floating_point),
+ content_type=DEFAULT_PCM_FORMAT.content_type,
sample_rate=queue_item.streamdetails.audio_format.sample_rate,
- bit_depth=bit_depth,
+ bit_depth=DEFAULT_PCM_FORMAT.bit_depth,
channels=2,
)
chunk_num = 0
player.player_id, CONF_SAMPLE_RATES
)
supported_sample_rates: tuple[int] = tuple(x[0] for x in supported_rates_conf)
- supported_bit_depths: tuple[int] = tuple(x[1] for x in supported_rates_conf)
- player_max_bit_depth = max(supported_bit_depths)
- for sample_rate in (192000, 96000, 48000, 44100):
+ for sample_rate in (192000, 96000, DEFAULT_PCM_FORMAT.sample_rate):
if sample_rate in supported_sample_rates:
output_sample_rate = sample_rate
break
- if self.mass.config.get_raw_player_config_value(
- player.player_id, CONF_VOLUME_NORMALIZATION, True
- ):
- # prefer f32 when volume normalization is enabled
- output_bit_depth = 32
- floating_point = True
- else:
- output_bit_depth = min(24, player_max_bit_depth)
- floating_point = False
return AudioFormat(
- content_type=ContentType.from_bit_depth(output_bit_depth, floating_point),
+ content_type=DEFAULT_PCM_FORMAT.content_type,
sample_rate=output_sample_rate,
- bit_depth=output_bit_depth,
+ bit_depth=DEFAULT_PCM_FORMAT.bit_depth,
channels=2,
)
*filter_params,
]
- # determine if we need to do resampling
- if (
- input_format.sample_rate != output_format.sample_rate
- or input_format.bit_depth > output_format.bit_depth
+ # determine if we need to do resampling (or dithering)
+ if input_format.sample_rate != output_format.sample_rate or (
+ input_format.bit_depth > 16 and output_format.bit_depth == 16
):
libsoxr_support = get_global_cache_value(CACHE_ATTR_LIBSOXR_PRESENT)
# prefer resampling with libsoxr due to its high quality
resample_filter += f":osr={output_format.sample_rate}"
# bit depth conversion: apply dithering when going down to 16 bits
+ # this is only needed when we need to back to 16 bits
+ # when going from 32bits FP to 24 bits no dithering is needed
if output_format.bit_depth == 16 and input_format.bit_depth > 16:
resample_filter += ":osf=s16:dither_method=triangular_hp"
from music_assistant_models.enums import ContentType
from music_assistant_models.media_items import AudioFormat
+from music_assistant.constants import DEFAULT_PCM_FORMAT
+
DOMAIN = "airplay"
CONF_ENCRYPTION = "encryption"
FALLBACK_VOLUME = 20
AIRPLAY_FLOW_PCM_FORMAT = AudioFormat(
- content_type=ContentType.PCM_F32LE,
+ content_type=DEFAULT_PCM_FORMAT.content_type,
sample_rate=44100,
- bit_depth=32,
+ bit_depth=DEFAULT_PCM_FORMAT.bit_depth,
)
AIRPLAY_PCM_FORMAT = AudioFormat(
content_type=ContentType.from_bit_depth(16), sample_rate=44100, bit_depth=16
CONF_GROUP_MEMBERS,
CONF_HTTP_PROFILE,
CONF_SAMPLE_RATES,
+ DEFAULT_PCM_FORMAT,
create_sample_rates_config_entry,
)
from music_assistant.controllers.streams import DEFAULT_STREAM_HEADERS
UGP_FORMAT = AudioFormat(
- content_type=ContentType.PCM_F32LE,
- sample_rate=48000,
- bit_depth=32,
+ content_type=DEFAULT_PCM_FORMAT.content_type,
+ sample_rate=DEFAULT_PCM_FORMAT.sample_rate,
+ bit_depth=DEFAULT_PCM_FORMAT.bit_depth,
)
# ruff: noqa: ARG002
CONF_ENTRY_SYNC_ADJUST,
CONF_PORT,
CONF_SYNC_ADJUST,
+ DEFAULT_PCM_FORMAT,
VERBOSE_LOG_LEVEL,
create_sample_rates_config_entry,
)
# this is a syncgroup, we need to handle this with a multi client stream
master_audio_format = AudioFormat(
- content_type=ContentType.PCM_F32LE,
- sample_rate=48000,
- bit_depth=32,
+ content_type=DEFAULT_PCM_FORMAT.content_type,
+ sample_rate=DEFAULT_PCM_FORMAT.sample_rate,
+ bit_depth=DEFAULT_PCM_FORMAT.bit_depth,
)
if media.media_type == MediaType.ANNOUNCEMENT:
# special case: stream announcement
CONF_ENTRY_CROSSFADE,
CONF_ENTRY_CROSSFADE_DURATION,
CONF_ENTRY_FLOW_MODE_ENFORCED,
+ DEFAULT_PCM_FORMAT,
create_sample_rates_config_entry,
)
from music_assistant.helpers.audio import FFMpeg, get_ffmpeg_stream, get_player_filter_params
start_queue_item=self.mass.player_queues.get_item(
media.queue_id, media.queue_item_id
),
- pcm_format=input_format,
+ pcm_format=DEFAULT_PCM_FORMAT,
)
else:
# assume url or some other direct path