blob: 7ef5fa99c3266e38efd6e2cdc1ed89df9985cdaf [file] [log] [blame]
/*
* 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 <math.h>
#include <stdbool.h>
#include "include/alsa_conformance_debug.h"
#include "include/alsa_conformance_helper.h"
#include "include/alsa_conformance_recorder.h"
#include "include/alsa_conformance_thread.h"
#include "include/alsa_conformance_timer.h"
extern int DEBUG_MODE;
extern int STRICT_MODE;
struct dev_thread {
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
char *dev_name;
snd_pcm_stream_t stream;
unsigned int channels;
snd_pcm_format_t format;
unsigned int rate;
snd_pcm_uframes_t period_size;
unsigned int block_size;
double duration;
unsigned underrun_count; /* Record number of underruns during playback. */
unsigned overrun_count; /* Record number of overrun during capture. */
struct alsa_conformance_timer *timer;
struct alsa_conformance_recorder *recorder;
};
struct dev_thread *dev_thread_create()
{
struct dev_thread *thread;
thread = (struct dev_thread*) malloc(sizeof(struct dev_thread));
if (!thread) {
perror("malloc (dev_thread)");
exit(EXIT_FAILURE);
}
thread->handle = NULL;
thread->params = NULL;
thread->dev_name = NULL;
thread->underrun_count = 0;
thread->overrun_count = 0;
thread->timer = conformance_timer_create();
thread->recorder = recorder_create();
return thread;
}
void dev_thread_destroy(struct dev_thread *thread)
{
assert(thread->handle);
free(thread->dev_name);
snd_pcm_hw_params_free(thread->params);
alsa_helper_close(thread->handle);
conformance_timer_destroy(thread->timer);
recorder_destroy(thread->recorder);
free(thread);
}
void dev_thread_set_stream(struct dev_thread *thread, snd_pcm_stream_t stream)
{
thread->stream = stream;
}
void dev_thread_set_dev_name(struct dev_thread *thread, const char *name)
{
free(thread->dev_name);
thread->dev_name = strdup(name);
}
void dev_thread_set_channels(struct dev_thread *thread, unsigned int channels)
{
thread->channels = channels;
}
void dev_thread_set_format(struct dev_thread *thread, snd_pcm_format_t format)
{
thread->format = format;
}
void dev_thread_set_format_from_str(struct dev_thread *thread,
const char *format_str)
{
snd_pcm_format_t format;
format = snd_pcm_format_value(format_str);
if (format == SND_PCM_FORMAT_UNKNOWN) {
fprintf(stderr, "unknown format: %s\n", format_str);
exit(EXIT_FAILURE);
}
thread->format = format;
}
void dev_thread_set_rate(struct dev_thread *thread, unsigned int rate)
{
thread->rate = rate;
}
void dev_thread_set_period_size(struct dev_thread *thread,
snd_pcm_uframes_t period_size)
{
thread->period_size = period_size;
}
void dev_thread_set_block_size(struct dev_thread *thread, unsigned int size)
{
thread->block_size = size;
}
void dev_thread_set_duration(struct dev_thread *thread, double duration)
{
thread->duration = duration;
}
void dev_thread_device_open(struct dev_thread *thread)
{
int rc;
assert(thread->dev_name);
rc = alsa_helper_open(thread->timer,
&thread->handle,
&thread->params,
thread->dev_name,
thread->stream);
if (rc < 0)
exit(EXIT_FAILURE);
}
void dev_thread_set_params(struct dev_thread *thread)
{
unsigned int rate;
snd_pcm_uframes_t period_size;
int rc;
assert(thread->handle);
rate = thread->rate;
period_size = thread->period_size;
rc = alsa_helper_set_hw_params(thread->timer,
thread->handle,
thread->params,
thread->format,
thread->channels,
&thread->rate,
&thread->period_size);
if (rc < 0)
exit(EXIT_FAILURE);
if (STRICT_MODE) {
if (rate != thread->rate) {
fprintf(stderr, "%s want to set rate %u but get %u.\n",
thread->dev_name, rate, thread->rate);
exit(EXIT_FAILURE);
}
if (period_size != thread->period_size) {
fprintf(stderr, "%s want to set period_size %lu but get %lu.\n",
thread->dev_name, period_size, thread->period_size);
exit(EXIT_FAILURE);
}
}
rc = alsa_helper_set_sw_param(thread->timer,
thread->handle);
if (rc < 0)
exit(EXIT_FAILURE);
}
void dev_thread_run_playback(struct dev_thread *thread)
{
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t block_size;
snd_pcm_uframes_t frames_to_write;
snd_pcm_sframes_t frames_avail;
snd_pcm_sframes_t frames_written;
snd_pcm_sframes_t frames_left;
snd_pcm_sframes_t frames_played;
snd_pcm_sframes_t frames_diff;
snd_pcm_t *handle;
struct timespec now;
struct timespec ori;
struct timespec relative_ts;
struct alsa_conformance_timer *timer;
uint8_t *buf;
/* These variables are for debug usage. */
char *time_str;
struct timespec prev;
struct timespec time_diff;
double rate;
handle = thread->handle;
timer = thread->timer;
block_size = (snd_pcm_uframes_t) thread->block_size;
if (alsa_helper_prepare(handle) < 0)
exit(EXIT_FAILURE);
/* Get device buffer size. */
snd_pcm_get_params(handle, &buffer_size, &period_size);
/* Check block_size is available. */
if (block_size == 0 || block_size > buffer_size / 2) {
fprintf(stderr,
"Block size %lu and buffer size %lu is not supported\n",
block_size, buffer_size);
exit(EXIT_FAILURE);
}
/* We need to allocate a zero buffer which will be written into device. */
buf = (uint8_t*) calloc(block_size * 2,
snd_pcm_format_physical_width(thread->format) / 8
* thread->channels);
if (!buf) {
perror("calloc");
exit(EXIT_FAILURE);
}
/* Calculate how many frames we need to write by duration * rate. */
frames_to_write = (snd_pcm_uframes_t)
round(thread->duration * (double) thread->rate);
/* First, we write 2 blocks into buffer. */
alsa_helper_write(handle, buf, 2 * block_size);
frames_written = 2 * block_size;
frames_played = 0;
alsa_helper_start(timer, handle);
/* Get the timestamp of beginning. */
clock_gettime(CLOCK_MONOTONIC_RAW, &ori);
if (DEBUG_MODE) {
prev = ori;
logger("%-13s %10s %10s %10s %18s\n", "TIME_DIFF(s)"
, "HW_LEVEL"
, "PLAYED"
, "DIFF"
, "RATE");
}
/*
* Get available frames without sleep. It's more accurate but consume
* more cpu time. Maybe we can choose other method in order to handle
* multithread test in the future.
*/
while (1) {
frames_avail = alsa_helper_avail(timer, handle);
frames_left = buffer_size - frames_avail;
/*
* Add a point into recorder when number of frames been played changes.
*/
if (frames_played != frames_written - frames_left) {
frames_diff = frames_written - frames_left - frames_played;
frames_played = frames_written - frames_left;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
relative_ts = now;
subtract_timespec(&relative_ts, &ori);
recorder_add(thread->recorder, relative_ts, frames_played);
/* In debug mode, print each point in details. */
if (DEBUG_MODE) {
time_diff = now;
subtract_timespec(&time_diff, &prev);
time_str = timespec_to_str(&time_diff);
rate = (double) frames_diff / timespec_to_s(&time_diff);
logger("%-13s %10ld %10ld %10ld %18lf\n", time_str
, frames_left
, frames_played
, frames_diff
, rate);
free(time_str);
prev = now;
}
}
/*
* Because time of writing frames into buffer is much smaller than
* time interval of device consuming frames, we can write frames here
* without affecting result.
*/
if (frames_left <= (long) block_size) {
if (frames_written >= frames_to_write)
break;
if (frames_left < 0)
thread->underrun_count++;
if (alsa_helper_write(handle, buf, block_size) < 0)
exit(EXIT_FAILURE);
frames_written += block_size;
}
}
alsa_helper_drop(handle);
free(buf);
}
void dev_thread_run_capture(struct dev_thread *thread)
{
snd_pcm_uframes_t block_size;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t frames_to_read;
snd_pcm_sframes_t frames_avail;
snd_pcm_sframes_t frames_diff;
snd_pcm_sframes_t frames_read;
snd_pcm_sframes_t old_frames_avail;
snd_pcm_t *handle;
struct timespec now;
struct timespec ori;
struct timespec relative_ts;
struct alsa_conformance_timer *timer;
uint8_t *buf;
/* These variables are for debug usage. */
char *time_str;
struct timespec prev;
struct timespec time_diff;
double rate;
handle = thread->handle;
timer = thread->timer;
block_size = (snd_pcm_uframes_t) thread->block_size;
if (alsa_helper_prepare(handle) < 0)
exit(EXIT_FAILURE);
/* Get device buffer size. */
snd_pcm_get_params(handle, &buffer_size, &period_size);
/* Check block_size is available. */
if (block_size == 0 || block_size > buffer_size) {
fprintf(stderr,
"Block size %lu and buffer size %lu is not supported\n",
block_size, buffer_size);
exit(EXIT_FAILURE);
}
/* We need to allocate buffer which will save data from device. */
buf = (uint8_t*) calloc(buffer_size ,
snd_pcm_format_physical_width(thread->format) / 8
* thread->channels);
if (!buf) {
perror("calloc");
exit(EXIT_FAILURE);
}
/* Calculate how many frames we need to read by duration * rate. */
frames_to_read = (snd_pcm_uframes_t)
round(thread->duration * (double) thread->rate);
frames_read = 0;
old_frames_avail = 0;
alsa_helper_start(timer, handle);
/* Get the timestamp of beginning. */
clock_gettime(CLOCK_MONOTONIC_RAW, &ori);
if (DEBUG_MODE) {
prev = ori;
logger("%-13s %10s %10s%18s\n", "TIME_DIFF(s)"
, "HW_LEVEL"
, "READ"
, "RATE");
}
while (frames_read < frames_to_read) {
frames_avail = alsa_helper_avail(timer, handle);
/* Check overrun. */
if (frames_avail > buffer_size)
thread->overrun_count++;
if (frames_avail != old_frames_avail) {
frames_diff = frames_avail - old_frames_avail;
old_frames_avail = frames_avail;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
relative_ts = now;
subtract_timespec(&relative_ts, &ori);
recorder_add(thread->recorder, relative_ts,
frames_read + frames_avail);
if (frames_avail >= block_size) {
if (alsa_helper_read(handle, buf, frames_avail) < 0)
exit(EXIT_FAILURE);
frames_read += frames_avail;
old_frames_avail = 0;
}
if (DEBUG_MODE) {
time_diff = now;
subtract_timespec(&time_diff, &prev);
time_str = timespec_to_str(&time_diff);
rate = (double) frames_diff / timespec_to_s(&time_diff);
logger("%-13s %10ld %10ld %18lf\n", time_str
, frames_avail
, frames_read
, rate);
free(time_str);
prev = now;
}
}
}
alsa_helper_drop(handle);
free(buf);
}
void dev_thread_run(struct dev_thread *thread)
{
if (thread->stream == SND_PCM_STREAM_PLAYBACK)
dev_thread_run_playback(thread);
else if (thread->stream == SND_PCM_STREAM_CAPTURE)
dev_thread_run_capture(thread);
}
void dev_thread_print_device_information(struct dev_thread *thread)
{
int rc;
assert(thread->handle);
rc = print_device_information(thread->handle, thread->params);
if (rc < 0)
exit(EXIT_FAILURE);
}
void dev_thread_print_params(struct dev_thread *thread)
{
int rc;
assert(thread->handle);
rc = print_params(thread->handle, thread->params);
if (rc < 0)
exit(EXIT_FAILURE);
}
void dev_thread_print_result(struct dev_thread* thread)
{
dev_thread_print_params(thread);
conformance_timer_print_result(thread->timer);
recorder_result(thread->recorder);
printf("number of underrun: %u\n", thread->underrun_count);
printf("number of overrun: %u\n", thread->overrun_count);
}