| /* |
| * Copyright 2018 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <alsa/asoundlib.h> |
| |
| #include "include/alsa_conformance_helper.h" |
| #include "include/alsa_conformance_timer.h" |
| |
| /* Print device information before setting params */ |
| int print_device_information(snd_pcm_t *handle, snd_pcm_hw_params_t *params) |
| { |
| unsigned int min; |
| unsigned int max; |
| unsigned int i; |
| /* The min2 and max2 are for period size and buffer size. The type of |
| * snd_pcm_uframes_t is unsigned long.*/ |
| snd_pcm_uframes_t min2; |
| snd_pcm_uframes_t max2; |
| int dir; |
| int rc; |
| |
| printf("PCM handle name: %s\n", snd_pcm_name(handle)); |
| |
| printf("PCM type: %s\n", snd_pcm_type_name(snd_pcm_type(handle))); |
| |
| printf("stream: %s\n", snd_pcm_stream_name(snd_pcm_stream(handle))); |
| |
| rc = snd_pcm_hw_params_get_channels_min(params, &min); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_channels_min: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_get_channels_max(params, &max); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_channels_max: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| printf("channels range: [%u, %u]\n", min, max); |
| |
| printf("available formats:"); |
| for (i = 0; i < SND_PCM_FORMAT_LAST; i++) { |
| rc = snd_pcm_hw_params_test_format( |
| handle, |
| params, |
| (snd_pcm_format_t) i); |
| if (rc == 0) |
| printf(" %s", snd_pcm_format_name((snd_pcm_format_t) i)); |
| } |
| puts(""); |
| |
| rc = snd_pcm_hw_params_get_rate_min(params, &min, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_rate_min: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_get_rate_max(params, &max, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_rate_max: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| printf("rate range: [%u, %u]\n", min, max); |
| |
| printf("available rates:"); |
| for (i = min; i <= max; i++) { |
| rc = snd_pcm_hw_params_test_rate(handle, params, i, 0); |
| if (rc == 0) |
| printf(" %d", i); |
| } |
| puts(""); |
| |
| rc = snd_pcm_hw_params_get_period_size_min(params, &min2, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_period_size_min: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_get_period_size_max(params, &max2, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_period_size_max: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| printf("period size range: [%lu, %lu]\n", min2, max2); |
| |
| rc = snd_pcm_hw_params_get_buffer_size_min(params, &min2); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_buffer_size_min: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_get_buffer_size_max(params, &max2); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_buffer_size_max: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| printf("buffer size range: [%lu, %lu]\n", min2, max2); |
| return 0; |
| } |
| |
| int print_params(snd_pcm_hw_params_t *params) |
| { |
| unsigned int val; |
| snd_pcm_uframes_t frames; |
| int rc; |
| int dir; |
| |
| rc = snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *) &val); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_access: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("access type: %s\n", snd_pcm_access_name((snd_pcm_access_t) val)); |
| |
| rc = snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*) &val); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_format: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("format: %s\n", snd_pcm_format_name((snd_pcm_format_t) val)); |
| |
| rc = snd_pcm_hw_params_get_channels(params, &val); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_channels: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("channels: %d\n", val); |
| |
| rc = snd_pcm_hw_params_get_rate(params, &val, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_rate: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("rate: %d bps\n", val); |
| |
| rc = snd_pcm_hw_params_get_period_time(params, &val, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_period_time: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("period time: %d us\n", val); |
| |
| rc = snd_pcm_hw_params_get_period_size(params, &frames, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_period_size: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("period size: %lu frames\n", frames); |
| |
| rc = snd_pcm_hw_params_get_buffer_time(params, &val, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_buffer_time: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("buffer time: %d us\n", val); |
| |
| rc = snd_pcm_hw_params_get_buffer_size(params, &frames); |
| if (rc < 0) { |
| fprintf(stderr, "hw_params_get_buffer_time: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| printf("buffer size: %lu frames\n", frames); |
| return 0; |
| } |
| |
| int alsa_helper_open(struct alsa_conformance_timer *timer, |
| snd_pcm_t **handle, |
| snd_pcm_hw_params_t **params, |
| const char *dev_name, |
| snd_pcm_stream_t stream) |
| { |
| int rc; |
| conformance_timer_start(timer, SND_PCM_OPEN); |
| rc = snd_pcm_open(handle, |
| dev_name, |
| stream, |
| SND_PCM_NONBLOCK | |
| SND_PCM_NO_AUTO_RESAMPLE | |
| SND_PCM_NO_AUTO_CHANNELS | |
| SND_PCM_NO_AUTO_FORMAT); |
| conformance_timer_stop(timer, SND_PCM_OPEN); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_open %s: %s\n", dev_name, snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* malloc params object*/ |
| rc = snd_pcm_hw_params_malloc(params); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_malloc: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* set default value */ |
| conformance_timer_start(timer, SND_PCM_HW_PARAMS_ANY); |
| rc = snd_pcm_hw_params_any(*handle, *params); |
| conformance_timer_stop(timer, SND_PCM_HW_PARAMS_ANY); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_any: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int alsa_helper_close(snd_pcm_t *handle) |
| { |
| int rc = snd_pcm_close(handle); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_close: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| return 0; |
| } |
| |
| int alsa_helper_set_hw_params(struct alsa_conformance_timer *timer, |
| snd_pcm_t *handle, |
| snd_pcm_hw_params_t *params, |
| snd_pcm_format_t format, |
| unsigned int channels, |
| unsigned int *rate, |
| snd_pcm_uframes_t *period_size) |
| { |
| int dir = 0; |
| int rc; |
| |
| /* Disable hardware resampling. */ |
| rc = snd_pcm_hw_params_set_rate_resample(handle, params, 0); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_rate_resample: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Always interleaved. */ |
| rc = snd_pcm_hw_params_set_access(handle, params, |
| SND_PCM_ACCESS_MMAP_INTERLEAVED); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_access: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_set_format(handle, params, format); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_format %s: %s\n", |
| snd_pcm_format_name(format), snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_hw_params_set_channels(handle, params, channels); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_channels %u: %s\n", channels, |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Set rate near, the rate may be changed if it's not supported */ |
| rc = snd_pcm_hw_params_set_rate_near(handle, params, rate, &dir); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_rate_near %u: %s\n", *rate, |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Set period size near, the period size may be changed if it's not |
| * supported */ |
| rc = snd_pcm_hw_params_set_period_size_near(handle, |
| params, |
| period_size, |
| &dir); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params_set_period_size_near %lu: %s\n", |
| *period_size, snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* TODO(yuhsuan): We should support setting buffer_size in the future. |
| * It's set automatically now.*/ |
| |
| conformance_timer_start(timer, SND_PCM_HW_PARAMS); |
| rc = snd_pcm_hw_params(handle, params); |
| conformance_timer_stop(timer, SND_PCM_HW_PARAMS); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_hw_params: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int alsa_helper_set_sw_param(struct alsa_conformance_timer *timer, |
| snd_pcm_t *handle) |
| { |
| snd_pcm_sw_params_t *swparams; |
| snd_pcm_uframes_t boundary; |
| int rc; |
| |
| snd_pcm_sw_params_alloca(&swparams); |
| |
| rc = snd_pcm_sw_params_current(handle, swparams); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params_current: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| rc = snd_pcm_sw_params_get_boundary(swparams, &boundary); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params_get_boundary: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Don't stop automatically. */ |
| rc = snd_pcm_sw_params_set_stop_threshold(handle, swparams, boundary); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params_set_stop_threshold: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Don't start automatically. */ |
| rc = snd_pcm_sw_params_set_start_threshold(handle, swparams, boundary); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params_set_start_threshold: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| /* Disable period events. */ |
| rc = snd_pcm_sw_params_set_period_event(handle, swparams, 0); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params_set_period_event: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| |
| conformance_timer_start(timer, SND_PCM_SW_PARAMS); |
| rc = snd_pcm_sw_params(handle, swparams); |
| conformance_timer_stop(timer, SND_PCM_SW_PARAMS); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_sw_params: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int alsa_helper_prepare(snd_pcm_t *handle) |
| { |
| int rc; |
| rc = snd_pcm_prepare(handle); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_prepare: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| return 0; |
| } |
| |
| int alsa_helper_start(struct alsa_conformance_timer *timer, |
| snd_pcm_t *handle) |
| { |
| int rc; |
| conformance_timer_start(timer, SND_PCM_START); |
| rc = snd_pcm_start(handle); |
| conformance_timer_stop(timer, SND_PCM_START); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_start: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int alsa_helper_drop(snd_pcm_t *handle) |
| { |
| int rc; |
| rc = snd_pcm_drop(handle); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_drop: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| snd_pcm_sframes_t alsa_helper_avail(struct alsa_conformance_timer *timer, |
| snd_pcm_t *handle) |
| { |
| snd_pcm_sframes_t rc; |
| |
| conformance_timer_start(timer, SND_PCM_AVAIL); |
| rc = snd_pcm_avail(handle); |
| conformance_timer_stop(timer, SND_PCM_AVAIL); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_avail: %s\n", snd_strerror(rc)); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| int alsa_helper_write(snd_pcm_t *handle, uint8_t *buf, snd_pcm_uframes_t size) |
| { |
| const snd_pcm_channel_area_t *my_areas; |
| snd_pcm_uframes_t frames, offset; |
| uint8_t *dst; |
| int frame_bytes; |
| int rc; |
| |
| while (size > 0) { |
| frames = size; |
| rc = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_mmap_begin: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| frame_bytes = my_areas[0].step / 8; |
| dst = (uint8_t *)my_areas[0].addr + offset * frame_bytes; |
| memcpy(dst, buf, frames * frame_bytes); |
| rc = snd_pcm_mmap_commit(handle, offset, frames); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_mmap_commit: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| size -= frames; |
| buf += frames * frame_bytes; |
| } |
| return 0; |
| } |
| |
| int alsa_helper_read(snd_pcm_t *handle, uint8_t *buf, snd_pcm_uframes_t size) |
| { |
| const snd_pcm_channel_area_t *my_areas; |
| snd_pcm_uframes_t frames, offset; |
| uint8_t *dst; |
| int frame_bytes; |
| int rc; |
| |
| while (size > 0) { |
| frames = size; |
| rc = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_mmap_begin: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| frame_bytes = my_areas[0].step / 8; |
| dst = (uint8_t *)my_areas[0].addr + offset * frame_bytes; |
| memcpy(buf, dst, frames * frame_bytes); |
| rc = snd_pcm_mmap_commit(handle, offset, frames); |
| if (rc < 0) { |
| fprintf(stderr, "snd_pcm_mmap_commit: %s\n", |
| snd_strerror(rc)); |
| return rc; |
| } |
| size -= frames; |
| buf += frames * frame_bytes; |
| } |
| return 0; |
| } |