mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 19:21:06 +00:00
alsa: Add support for DSD audio
Code is partially based on the DSD of Robert Tiemann <rtie@gmx.de>: https://gitlab.freedesktop.org/rtiemann/gstreamer/-/tree/dsd Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3901>
This commit is contained in:
parent
34238e251d
commit
fd25e24217
3 changed files with 269 additions and 6 deletions
|
@ -110,7 +110,7 @@
|
|||
"long-name": "Audio sink (ALSA)",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "audio/x-raw:\n format: { F64LE, F64BE, F32LE, F32BE, S32LE, S32BE, U32LE, U32BE, S24_32LE, S24_32BE, U24_32LE, U24_32BE, S24LE, S24BE, U24LE, U24BE, S20LE, S20BE, U20LE, U20BE, S18LE, S18BE, U18LE, U18BE, S16LE, S16BE, U16LE, U16BE, S8, U8 }\n layout: interleaved\n rate: [ 1, 2147483647 ]\n channels: [ 1, 2147483647 ]\naudio/x-ac3:\n framed: true\naudio/x-eac3:\n framed: true\naudio/x-dts:\n framed: true\n block-size: { (int)512, (int)1024, (int)2048 }\naudio/mpeg:\n mpegversion: 1\nmpegaudioversion: [ 1, 3 ]\n parsed: true\n",
|
||||
"caps": "audio/x-raw:\n format: { F64LE, F64BE, F32LE, F32BE, S32LE, S32BE, U32LE, U32BE, S24_32LE, S24_32BE, U24_32LE, U24_32BE, S24LE, S24BE, U24LE, U24BE, S20LE, S20BE, U20LE, U20BE, S18LE, S18BE, U18LE, U18BE, S16LE, S16BE, U16LE, U16BE, S8, U8 }\n layout: interleaved\n rate: [ 1, 2147483647 ]\n channels: [ 1, 2147483647 ]\naudio/x-dsd:\n format: { DSDU32BE, DSDU16BE, DSDU8, DSDU32LE, DSDU16LE }\n layout: interleaved\n reversed-bytes: false\n rate: [ 1, 2147483647 ]\n channels: [ 1, 2147483647 ]\naudio/x-ac3:\n framed: true\naudio/x-eac3:\n framed: true\naudio/x-dts:\n framed: true\n block-size: { (int)512, (int)1024, (int)2048 }\naudio/mpeg:\n mpegversion: 1\nmpegaudioversion: [ 1, 3 ]\n parsed: true\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "gstalsa.h"
|
||||
|
||||
#include <gst/audio/audio.h>
|
||||
#include <gst/audio/gstdsd.h>
|
||||
|
||||
static GstCaps *
|
||||
gst_alsa_detect_rates (GstObject * obj, snd_pcm_hw_params_t * hw_params,
|
||||
|
@ -238,6 +239,209 @@ gst_alsa_detect_formats (GstObject * obj, snd_pcm_hw_params_t * hw_params,
|
|||
return caps;
|
||||
}
|
||||
|
||||
/* Notes about what the "rate" means in DSD:
|
||||
*
|
||||
* In DSD, "sample formats" don't actually exist. There is only the DSD bit;
|
||||
* this is what could be considered the closest equivalent to a "sample format".
|
||||
* But since it is impractical to deal with individual bits in software, the
|
||||
* bits are typically grouped into words (8/16/32 bit words). These are the
|
||||
* DSDU8, DSDU16LE etc. "grouping formats".
|
||||
*
|
||||
* The "rate" in DSD information refers to the number of DSD _bytes_ per second
|
||||
* (not bits per second, because, as said, per-bit handling in software does
|
||||
* not usually make sense). ALSA however interprets "rate" as the number of
|
||||
* DSD _words_ per minute. If the word format is DSDU8, then there's no difference.
|
||||
* But if for example it is DSDU16LE, then ALSA's rate is half of the rate
|
||||
* from GstDsdInfo. For this reason, before setting the rate in the ALSA
|
||||
* hw params, it is essential to divide the rate from the DSD info by the
|
||||
* word length (in bytes).
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
snd_pcm_format_t alsa_format;
|
||||
const char *gstreamer_format_name;
|
||||
} DsdFormatInfo;
|
||||
|
||||
static GstCaps *
|
||||
gst_alsa_detect_dsd_formats (GstObject * obj, snd_pcm_hw_params_t * hw_params)
|
||||
{
|
||||
snd_pcm_format_mask_t *mask;
|
||||
GValue format_list_value = G_VALUE_INIT;
|
||||
gint table_idx;
|
||||
gboolean dsd_is_supported = FALSE;
|
||||
GstCaps *caps = NULL;
|
||||
|
||||
const DsdFormatInfo format_table[] = {
|
||||
{SND_PCM_FORMAT_DSD_U8, "DSDU8"},
|
||||
{SND_PCM_FORMAT_DSD_U16_LE, "DSDU16LE"},
|
||||
{SND_PCM_FORMAT_DSD_U16_BE, "DSDU16BE"},
|
||||
{SND_PCM_FORMAT_DSD_U32_LE, "DSDU32LE"},
|
||||
{SND_PCM_FORMAT_DSD_U32_BE, "DSDU32BE"}
|
||||
};
|
||||
const gint format_table_size = sizeof (format_table) / sizeof (DsdFormatInfo);
|
||||
|
||||
g_value_init (&format_list_value, GST_TYPE_LIST);
|
||||
|
||||
snd_pcm_format_mask_malloc (&mask);
|
||||
snd_pcm_hw_params_get_format_mask (hw_params, mask);
|
||||
|
||||
for (table_idx = 0; table_idx < format_table_size; ++table_idx) {
|
||||
const DsdFormatInfo *format_info = &(format_table[table_idx]);
|
||||
gboolean format_supported = snd_pcm_format_mask_test (mask,
|
||||
format_info->alsa_format);
|
||||
|
||||
GST_DEBUG_OBJECT (obj, "%s supported: %s",
|
||||
format_info->gstreamer_format_name, format_supported ? "yes" : "no");
|
||||
|
||||
if (format_supported) {
|
||||
GValue format_value = G_VALUE_INIT;
|
||||
|
||||
g_value_init (&format_value, G_TYPE_STRING);
|
||||
g_value_set_string (&format_value, format_info->gstreamer_format_name);
|
||||
gst_value_list_append_and_take_value (&format_list_value, &format_value);
|
||||
|
||||
dsd_is_supported = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (dsd_is_supported) {
|
||||
GstStructure *structure;
|
||||
structure = gst_structure_new_empty ("audio/x-dsd");
|
||||
|
||||
/* As a small optimization, if we only support exactly one
|
||||
* format, store it directly instead of an 1-item list. */
|
||||
|
||||
if (gst_value_list_get_size (&format_list_value) == 1) {
|
||||
const GValue *supported_format_value =
|
||||
gst_value_list_get_value (&format_list_value, 0);
|
||||
|
||||
gst_structure_set_value (structure, "format", supported_format_value);
|
||||
g_value_unset (&format_list_value);
|
||||
} else
|
||||
gst_structure_take_value (structure, "format", &format_list_value);
|
||||
|
||||
caps = gst_caps_new_full (structure, NULL);
|
||||
} else {
|
||||
g_value_unset (&format_list_value);
|
||||
}
|
||||
|
||||
snd_pcm_format_mask_free (mask);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_alsa_detect_dsd_rates (GstObject * obj, snd_pcm_t * handle,
|
||||
snd_pcm_hw_params_t * hw_params, GstCaps * in_caps)
|
||||
{
|
||||
GstCaps *caps = NULL;
|
||||
guint min_rate, max_rate;
|
||||
gint err, dir, caps_idx;
|
||||
int cur_dsd_multiplier;
|
||||
gboolean keep_testing_rates;
|
||||
GValue rate_list_value = G_VALUE_INIT;
|
||||
GValue rate_value = G_VALUE_INIT;
|
||||
|
||||
GST_LOG_OBJECT (obj, "probing DSD sample rates ...");
|
||||
|
||||
g_value_init (&rate_list_value, GST_TYPE_LIST);
|
||||
g_value_init (&rate_value, G_TYPE_INT);
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_rate_min (hw_params, &min_rate, &dir)) < 0)
|
||||
goto min_rate_err;
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_rate_max (hw_params, &max_rate, &dir)) < 0)
|
||||
goto max_rate_err;
|
||||
|
||||
/* In DSD, valid rates are an integer multiple of 44100 (DSD-44x) or
|
||||
* 48000 (DSD-48x), and those multipliers must themselves be a power of
|
||||
* 2. For example, "DSD64-44x" means 64*44100 = 2822400 bits per second.
|
||||
* In software, we use bytes, so DSD64-44x equals 2822400/8 = 352800 bytes
|
||||
* per second. DSD64 is the lowest valid rate. The next higher valid rate
|
||||
* would be DSD128-4x, and DSD256-44x after that etc. DSD200-44x is not
|
||||
* valid, for example. For this reason, it makes sense to check for the
|
||||
* individual valid rates that lie within the range defined by min_rate
|
||||
* and max_rate. */
|
||||
|
||||
cur_dsd_multiplier = ((gint64) min_rate) * 8 / 44100;
|
||||
/* Multipliers below 64 are not valid. If the hardware can't handle
|
||||
* at least DSD64-44x, we can't play DSD, so this is a good starting
|
||||
* point for the rate tests below. */
|
||||
if (cur_dsd_multiplier < 64)
|
||||
cur_dsd_multiplier = 64;
|
||||
|
||||
keep_testing_rates = TRUE;
|
||||
while (keep_testing_rates) {
|
||||
const int rates_to_test[] = {
|
||||
GST_DSD_MAKE_DSD_RATE_44x (cur_dsd_multiplier),
|
||||
GST_DSD_MAKE_DSD_RATE_48x (cur_dsd_multiplier)
|
||||
};
|
||||
const gchar *rates_desc[] = { "44x", "48x" };
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (rates_to_test); ++i) {
|
||||
int rate_to_test = rates_to_test[i];
|
||||
if (rate_to_test > max_rate) {
|
||||
keep_testing_rates = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_test_rate (handle, hw_params, rate_to_test, 0) == 0) {
|
||||
GST_DEBUG_OBJECT (obj,
|
||||
"DSD%d-%s available (equals rate of %d DSD bytes per second)",
|
||||
cur_dsd_multiplier, rates_desc[i], rate_to_test);
|
||||
g_value_set_int (&rate_value, rate_to_test);
|
||||
gst_value_list_append_value (&rate_list_value, &rate_value);
|
||||
}
|
||||
}
|
||||
|
||||
cur_dsd_multiplier *= 2;
|
||||
}
|
||||
|
||||
caps = gst_caps_make_writable (in_caps);
|
||||
|
||||
if (gst_value_list_get_size (&rate_list_value) == 1) {
|
||||
/* As a small optimization, if we only support exactly one
|
||||
* rate, store it directly instead of an 1-item list. */
|
||||
|
||||
const GValue *supported_rate_value =
|
||||
gst_value_list_get_value (&rate_list_value, 0);
|
||||
|
||||
for (caps_idx = 0; caps_idx < gst_caps_get_size (caps); ++caps_idx) {
|
||||
GstStructure *structure = gst_caps_get_structure (caps, caps_idx);
|
||||
gst_structure_set_value (structure, "rate", supported_rate_value);
|
||||
}
|
||||
} else {
|
||||
for (caps_idx = 0; caps_idx < gst_caps_get_size (caps); ++caps_idx) {
|
||||
GstStructure *structure = gst_caps_get_structure (caps, caps_idx);
|
||||
gst_structure_set_value (structure, "rate", &rate_list_value);
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
g_value_unset (&rate_list_value);
|
||||
g_value_unset (&rate_value);
|
||||
return caps;
|
||||
|
||||
/* ERRORS */
|
||||
min_rate_err:
|
||||
{
|
||||
GST_ERROR_OBJECT (obj, "failed to query minimum sample rate: %s",
|
||||
snd_strerror (err));
|
||||
gst_caps_unref (in_caps);
|
||||
goto finish;
|
||||
}
|
||||
max_rate_err:
|
||||
{
|
||||
GST_ERROR_OBJECT (obj, "failed to query maximum sample rate: %s",
|
||||
snd_strerror (err));
|
||||
gst_caps_unref (in_caps);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
/* we don't have channel mappings for more than this many channels */
|
||||
#define GST_ALSA_MAX_CHANNELS 8
|
||||
|
||||
|
@ -498,6 +702,7 @@ gst_alsa_probe_supported_formats (GstObject * obj, gchar * device,
|
|||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_stream_t stream_type;
|
||||
GstCaps *caps;
|
||||
GstCaps *dsd_caps;
|
||||
gint err;
|
||||
|
||||
snd_pcm_hw_params_malloc (&hw_params);
|
||||
|
@ -506,26 +711,55 @@ gst_alsa_probe_supported_formats (GstObject * obj, gchar * device,
|
|||
|
||||
stream_type = snd_pcm_stream (handle);
|
||||
|
||||
/* Try detecting PCM */
|
||||
|
||||
caps = gst_alsa_detect_formats (obj, hw_params,
|
||||
gst_caps_copy (template_caps), G_BYTE_ORDER);
|
||||
|
||||
/* if there are no formats in native endianness, try non-native as well */
|
||||
if (caps == NULL) {
|
||||
GST_INFO_OBJECT (obj, "no formats in native endianness detected");
|
||||
GST_INFO_OBJECT (obj, "no PCM formats in native endianness detected");
|
||||
|
||||
caps = gst_alsa_detect_formats (obj, hw_params,
|
||||
gst_caps_copy (template_caps),
|
||||
(G_BYTE_ORDER == G_LITTLE_ENDIAN) ? G_BIG_ENDIAN : G_LITTLE_ENDIAN);
|
||||
|
||||
if (caps == NULL)
|
||||
if (caps == NULL) {
|
||||
GST_ERROR_OBJECT (obj, "failed to detect PCM formats");
|
||||
goto subroutine_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(caps = gst_alsa_detect_rates (obj, hw_params, caps)))
|
||||
if (!(caps = gst_alsa_detect_rates (obj, hw_params, caps))) {
|
||||
GST_ERROR_OBJECT (obj, "failed to detect PCM rates");
|
||||
goto subroutine_error;
|
||||
}
|
||||
|
||||
if (!(caps = gst_alsa_detect_channels (obj, hw_params, caps)))
|
||||
if (!(caps = gst_alsa_detect_channels (obj, hw_params, caps))) {
|
||||
GST_ERROR_OBJECT (obj, "failed to detect PCM channels");
|
||||
goto subroutine_error;
|
||||
}
|
||||
|
||||
/* Try detecting DSD */
|
||||
dsd_caps = gst_alsa_detect_dsd_formats (obj, hw_params);
|
||||
if (dsd_caps != NULL) {
|
||||
GST_INFO_OBJECT (obj, "DSD support detected");
|
||||
|
||||
if (!(dsd_caps =
|
||||
gst_alsa_detect_dsd_rates (obj, handle, hw_params, dsd_caps))) {
|
||||
GST_ERROR_OBJECT (obj, "failed to detect DSD rates");
|
||||
goto subroutine_error;
|
||||
}
|
||||
|
||||
if (!(dsd_caps = gst_alsa_detect_channels (obj, hw_params, dsd_caps))) {
|
||||
GST_ERROR_OBJECT (obj, "failed to detect DSD channels");
|
||||
goto subroutine_error;
|
||||
}
|
||||
|
||||
gst_caps_append (caps, dsd_caps);
|
||||
} else {
|
||||
GST_INFO_OBJECT (obj, "DSD support not detected");
|
||||
}
|
||||
|
||||
/* Try opening IEC958 device to see if we can support that format (playback
|
||||
* only for now but we could add SPDIF capture later) */
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "gstalsasink.h"
|
||||
|
||||
#include <gst/audio/gstaudioiec61937.h>
|
||||
#include <gst/audio/gstdsd.h>
|
||||
#include <glib/gi18n-lib.h>
|
||||
|
||||
#ifndef ESTRPIPE
|
||||
|
@ -114,6 +115,11 @@ static GstStaticPadTemplate alsasink_sink_factory =
|
|||
"format = (string) " GST_AUDIO_FORMATS_ALL ", "
|
||||
"layout = (string) interleaved, "
|
||||
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; "
|
||||
GST_DSD_MEDIA_TYPE ", "
|
||||
"format = (string) " GST_DSD_FORMATS_ALL ", "
|
||||
"layout = (string) interleaved, "
|
||||
"reversed-bytes = (gboolean) false, "
|
||||
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; "
|
||||
PASSTHROUGH_CAPS)
|
||||
);
|
||||
|
||||
|
@ -825,6 +831,27 @@ alsasink_parse_spec (GstAlsaSink * alsa, GstAudioRingBufferSpec * spec)
|
|||
goto error;
|
||||
}
|
||||
break;
|
||||
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DSD:
|
||||
switch (GST_AUDIO_RING_BUFFER_SPEC_DSD_FORMAT (spec)) {
|
||||
case GST_DSD_FORMAT_U8:
|
||||
alsa->format = SND_PCM_FORMAT_DSD_U8;
|
||||
break;
|
||||
case GST_DSD_FORMAT_U16LE:
|
||||
alsa->format = SND_PCM_FORMAT_DSD_U16_LE;
|
||||
break;
|
||||
case GST_DSD_FORMAT_U16BE:
|
||||
alsa->format = SND_PCM_FORMAT_DSD_U16_BE;
|
||||
break;
|
||||
case GST_DSD_FORMAT_U32LE:
|
||||
alsa->format = SND_PCM_FORMAT_DSD_U32_LE;
|
||||
break;
|
||||
case GST_DSD_FORMAT_U32BE:
|
||||
alsa->format = SND_PCM_FORMAT_DSD_U32_BE;
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW:
|
||||
alsa->format = SND_PCM_FORMAT_A_LAW;
|
||||
break;
|
||||
|
@ -848,7 +875,9 @@ alsasink_parse_spec (GstAlsaSink * alsa, GstAudioRingBufferSpec * spec)
|
|||
alsa->period_time = spec->latency_time;
|
||||
alsa->access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
|
||||
if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW && alsa->channels < 9)
|
||||
if ((spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW ||
|
||||
spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DSD) &&
|
||||
alsa->channels < 9)
|
||||
gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SINK
|
||||
(alsa)->ringbuffer, alsa_position[alsa->channels - 1]);
|
||||
|
||||
|
|
Loading…
Reference in a new issue