/* 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 #include "buffer_share.h" #include "cras_audio_area.h" #include "cras_dsp_pipeline.h" #include "cras_mix.h" #include "cras_rstream.h" #include "cras_system_state.h" #include "dsp_util.h" #include "input_data.h" #include "utlist.h" void input_data_run(struct ext_dsp_module *ext, unsigned int nframes) { struct input_data *data = (struct input_data *)ext; float *const *wp; int i; unsigned int writable; unsigned int offset = 0; while (nframes) { writable = float_buffer_writable(data->fbuffer); writable = MIN(nframes, writable); if (!writable) { syslog(LOG_ERR, "Not enough space to process input data"); break; } wp = float_buffer_write_pointer(data->fbuffer); for (i = 0; i < data->fbuffer->num_channels; i++) memcpy(wp[i], ext->ports[i] + offset, writable * sizeof(float)); float_buffer_written(data->fbuffer, writable); nframes -= writable; offset += writable; } } void input_data_configure(struct ext_dsp_module *ext, unsigned int buffer_size, unsigned int num_channels, unsigned int rate) { struct input_data *data = (struct input_data *)ext; if (data->fbuffer) float_buffer_destroy(&data->fbuffer); data->fbuffer = float_buffer_create(buffer_size, num_channels); } struct input_data *input_data_create(void *dev_ptr) { struct input_data *data = (struct input_data *)calloc(1, sizeof(*data)); data->dev_ptr = dev_ptr; data->ext.run = input_data_run; data->ext.configure = input_data_configure; return data; } void input_data_destroy(struct input_data **data) { if ((*data)->fbuffer) float_buffer_destroy(&(*data)->fbuffer); free(*data); *data = NULL; } void input_data_set_all_streams_read(struct input_data *data, unsigned int nframes) { if (!data->fbuffer) return; if (float_buffer_level(data->fbuffer) < nframes) { syslog(LOG_ERR, "All streams read %u frames exceeds %u" " in input_data's buffer", nframes, float_buffer_level(data->fbuffer)); float_buffer_reset(data->fbuffer); return; } float_buffer_read(data->fbuffer, nframes); } /* * The logic is not trivial to return the cras_audio_area and offset for * a input stream to read. The buffer position and length of a bunch of * input member variables are described below. * * hw_ptr appl_ptr * a. buffer of input device: |------------------------| * b. fbuffer of input data: |<--------------->| * c. stream offset of input data: |<--------->| * stream offset of input data: |<-->| * stream offset of input data: |<------------->| * d. audio area of input data: |<----------->| * * One thing to keep in mind is, the offset could exceed the size of * buffer to read. It's not intuitive though why the stream offset would * exceed buffer size. Check this example: * * Idev gets input buffer 500 frames. One stream read 400, while the other * stream read 100. We track stream offset [0, 300] after both stream * consumes 100 frames. In the next wake up, audio thread asks idev to * get 250 frames. Now the input data holds audio area containing 250 frames * of audio as queried, while its float buffer contains 400 frames of audio * deinterleaved from last wake up. * * Wake up at T0: * hw_ptr appl_ptr * Input audio area |-------------------------------| * deinterleave float |-------------------------------| * Stream 1 read |------| * Stream 2 read |-----------------------| * * Wake up at T1: hw_ptr appl_ptr * Input audio area |------------| * deinterleave float |------------------------| * Stream 1 offset | * Stream 2 offset |----------------| * * Case 1: * A normal input stream, of read offset 0, about to read from device. * We shall return the exact audio area from idev, and set read offset to 0. * * Case 2: * A normal input stream, of read offset 300, about to read from device. * We shall return the exact audio area from idev but clip read offset to 250. * * Case 3: * An APM Stream of read offset 300, would like to read the deinterleaved * float buffer. We shall let APM process the float buffer from offset 300. * Don't bother clip read offset in this case, because fbuffer contains * the deepest deinterleaved audio data ever read from idev. */ int input_data_get_for_stream(struct input_data *data, struct cras_rstream *stream, struct buffer_share *offsets, struct cras_audio_area **area, unsigned int *offset) { int apm_processed; struct cras_apm *apm; int stream_offset = buffer_share_id_offset(offsets, stream->stream_id); apm = cras_apm_list_get_active_apm(stream, data->dev_ptr); if (apm == NULL) { /* * Case 1 and 2 from above example. */ *area = data->area; *offset = MIN(stream_offset, data->area->frames); } else { /* * Case 3 from above example. */ apm_processed = cras_apm_list_process(apm, data->fbuffer, stream_offset); if (apm_processed < 0) { cras_apm_list_remove_apm(stream->apm_list, apm); return 0; } buffer_share_offset_update(offsets, stream->stream_id, apm_processed); *area = cras_apm_list_get_processed(apm); *offset = 0; } return 0; } int input_data_put_for_stream(struct input_data *data, struct cras_rstream *stream, struct buffer_share *offsets, unsigned int frames) { struct cras_apm *apm = cras_apm_list_get_active_apm(stream, data->dev_ptr); if (apm) cras_apm_list_put_processed(apm, frames); else buffer_share_offset_update(offsets, stream->stream_id, frames); return 0; } float input_data_get_software_gain_scaler(struct input_data *data, float idev_sw_gain_scaler, struct cras_rstream *stream) { struct cras_apm *apm; /* * APM has more advanced gain control mechanism. If it is using tuned * settings, give APM total control of the captured samples without * additional gain scaler at all. */ apm = cras_apm_list_get_active_apm(stream, data->dev_ptr); if (apm && cras_apm_list_get_use_tuned_settings(apm)) return 1.0f; return idev_sw_gain_scaler * cras_rstream_get_volume_scaler(stream); }