Skip to content

Commit

Permalink
Chore: Simplify internal pcm format
Browse files Browse the repository at this point in the history
just always use 32 bits floating points
  • Loading branch information
marcelveldt committed Jan 16, 2025
1 parent c6c2cbc commit 97d55f9
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 56 deletions.
27 changes: 26 additions & 1 deletion music_assistant/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
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
Expand Down Expand Up @@ -523,3 +524,27 @@ def create_sample_rates_config_entry(
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,
)
50 changes: 8 additions & 42 deletions music_assistant/controllers/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@
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,
)
Expand Down Expand Up @@ -74,23 +75,6 @@
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)


Expand Down Expand Up @@ -346,17 +330,10 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
)

# 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
Expand Down Expand Up @@ -1081,24 +1058,13 @@ async def _select_flow_format(
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,
)
9 changes: 5 additions & 4 deletions music_assistant/helpers/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,9 @@ def get_ffmpeg_args(
*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
Expand All @@ -309,6 +308,8 @@ def get_ffmpeg_args(
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"

Expand Down
6 changes: 4 additions & 2 deletions music_assistant/providers/airplay/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
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"
Expand All @@ -22,9 +24,9 @@
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
Expand Down
7 changes: 4 additions & 3 deletions music_assistant/providers/player_group/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
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
Expand All @@ -75,9 +76,9 @@


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
Expand Down
7 changes: 4 additions & 3 deletions music_assistant/providers/slimproto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
CONF_ENTRY_SYNC_ADJUST,
CONF_PORT,
CONF_SYNC_ADJUST,
DEFAULT_PCM_FORMAT,
VERBOSE_LOG_LEVEL,
create_sample_rates_config_entry,
)
Expand Down Expand Up @@ -365,9 +366,9 @@ async def play_media(

# 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
Expand Down
3 changes: 2 additions & 1 deletion music_assistant/providers/snapcast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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
Expand Down Expand Up @@ -520,7 +521,7 @@ async def play_media(self, player_id: str, media: PlayerMedia) -> None:
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
Expand Down

0 comments on commit 97d55f9

Please sign in to comment.